activerecord 3.2.22.5 → 4.2.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (236) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1632 -609
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +37 -41
  5. data/examples/performance.rb +31 -19
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +56 -42
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -36
  10. data/lib/active_record/associations/association.rb +73 -55
  11. data/lib/active_record/associations/association_scope.rb +143 -82
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +125 -31
  15. data/lib/active_record/associations/builder/belongs_to.rb +89 -61
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +23 -17
  21. data/lib/active_record/associations/collection_association.rb +251 -177
  22. data/lib/active_record/associations/collection_proxy.rb +963 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +113 -22
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -39
  26. data/lib/active_record/associations/has_one_association.rb +43 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +62 -33
  38. data/lib/active_record/associations/preloader.rb +101 -79
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +30 -16
  41. data/lib/active_record/associations.rb +463 -345
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +142 -151
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +137 -57
  47. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +73 -106
  50. data/lib/active_record/attribute_methods/serialization.rb +44 -94
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
  52. data/lib/active_record/attribute_methods/write.rb +57 -44
  53. data/lib/active_record/attribute_methods.rb +301 -141
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +246 -217
  58. data/lib/active_record/base.rb +70 -474
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
  75. data/lib/active_record/connection_adapters/column.rb +31 -245
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
  114. data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +157 -105
  119. data/lib/active_record/dynamic_matchers.rb +119 -63
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +94 -36
  122. data/lib/active_record/explain.rb +15 -63
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +9 -5
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +302 -215
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +143 -70
  129. data/lib/active_record/integration.rb +65 -12
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +73 -52
  133. data/lib/active_record/locking/pessimistic.rb +5 -5
  134. data/lib/active_record/log_subscriber.rb +24 -21
  135. data/lib/active_record/migration/command_recorder.rb +124 -32
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +511 -213
  138. data/lib/active_record/model_schema.rb +91 -117
  139. data/lib/active_record/nested_attributes.rb +184 -130
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +276 -117
  143. data/lib/active_record/query_cache.rb +19 -37
  144. data/lib/active_record/querying.rb +28 -18
  145. data/lib/active_record/railtie.rb +73 -40
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +4 -3
  148. data/lib/active_record/railties/databases.rake +141 -416
  149. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  150. data/lib/active_record/readonly_attributes.rb +1 -4
  151. data/lib/active_record/reflection.rb +513 -154
  152. data/lib/active_record/relation/batches.rb +91 -43
  153. data/lib/active_record/relation/calculations.rb +199 -161
  154. data/lib/active_record/relation/delegation.rb +116 -25
  155. data/lib/active_record/relation/finder_methods.rb +362 -248
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -43
  160. data/lib/active_record/relation/query_methods.rb +928 -167
  161. data/lib/active_record/relation/spawn_methods.rb +48 -149
  162. data/lib/active_record/relation.rb +352 -207
  163. data/lib/active_record/result.rb +101 -10
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +56 -59
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +106 -63
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +50 -57
  170. data/lib/active_record/scoping/named.rb +73 -109
  171. data/lib/active_record/scoping.rb +58 -123
  172. data/lib/active_record/serialization.rb +6 -2
  173. data/lib/active_record/serializers/xml_serializer.rb +12 -22
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +168 -15
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +23 -16
  181. data/lib/active_record/transactions.rb +125 -79
  182. data/lib/active_record/type/big_integer.rb +13 -0
  183. data/lib/active_record/type/binary.rb +50 -0
  184. data/lib/active_record/type/boolean.rb +31 -0
  185. data/lib/active_record/type/date.rb +50 -0
  186. data/lib/active_record/type/date_time.rb +54 -0
  187. data/lib/active_record/type/decimal.rb +64 -0
  188. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  189. data/lib/active_record/type/decorator.rb +14 -0
  190. data/lib/active_record/type/float.rb +19 -0
  191. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  192. data/lib/active_record/type/integer.rb +59 -0
  193. data/lib/active_record/type/mutable.rb +16 -0
  194. data/lib/active_record/type/numeric.rb +36 -0
  195. data/lib/active_record/type/serialized.rb +62 -0
  196. data/lib/active_record/type/string.rb +40 -0
  197. data/lib/active_record/type/text.rb +11 -0
  198. data/lib/active_record/type/time.rb +26 -0
  199. data/lib/active_record/type/time_value.rb +38 -0
  200. data/lib/active_record/type/type_map.rb +64 -0
  201. data/lib/active_record/type/unsigned_integer.rb +15 -0
  202. data/lib/active_record/type/value.rb +110 -0
  203. data/lib/active_record/type.rb +23 -0
  204. data/lib/active_record/validations/associated.rb +24 -16
  205. data/lib/active_record/validations/presence.rb +67 -0
  206. data/lib/active_record/validations/uniqueness.rb +123 -64
  207. data/lib/active_record/validations.rb +36 -29
  208. data/lib/active_record/version.rb +5 -7
  209. data/lib/active_record.rb +66 -46
  210. data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
  211. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
  212. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  213. data/lib/rails/generators/active_record/migration.rb +11 -8
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
  215. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  216. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  217. data/lib/rails/generators/active_record.rb +3 -11
  218. metadata +101 -45
  219. data/examples/associations.png +0 -0
  220. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  221. data/lib/active_record/associations/join_helper.rb +0 -55
  222. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  223. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  226. data/lib/active_record/dynamic_finder_match.rb +0 -68
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/fixtures/file.rb +0 -65
  229. data/lib/active_record/identity_map.rb +0 -162
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -360
  232. data/lib/active_record/test_case.rb +0 -73
  233. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  234. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  235. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  236. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -17,6 +17,15 @@ module ActiveRecord
17
17
  64
18
18
  end
19
19
 
20
+ # Returns the maximum allowed length for an index name. This
21
+ # limit is enforced by rails and Is less than or equal to
22
+ # <tt>index_name_length</tt>. The gap between
23
+ # <tt>index_name_length</tt> is to allow internal rails
24
+ # operations to use prefixes in temporary operations.
25
+ def allowed_index_name_length
26
+ index_name_length
27
+ end
28
+
20
29
  # Returns the maximum length of an index name.
21
30
  def index_name_length
22
31
  64
@@ -1,33 +1,46 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters # :nodoc:
3
3
  module DatabaseStatements
4
+ def initialize
5
+ super
6
+ reset_transaction
7
+ end
8
+
4
9
  # Converts an arel AST to SQL
5
10
  def to_sql(arel, binds = [])
6
11
  if arel.respond_to?(:ast)
7
- visitor.accept(arel.ast) do
8
- quote(*binds.shift.reverse)
9
- end
12
+ collected = visitor.accept(arel.ast, collector)
13
+ collected.compile(binds.dup, self)
10
14
  else
11
15
  arel
12
16
  end
13
17
  end
14
18
 
15
- # Returns an array of record hashes with the column names as keys and
16
- # column values as values.
19
+ # This is used in the StatementCache object. It returns an object that
20
+ # can be used to query the database repeatedly.
21
+ def cacheable_query(arel) # :nodoc:
22
+ if prepared_statements
23
+ ActiveRecord::StatementCache.query visitor, arel.ast
24
+ else
25
+ ActiveRecord::StatementCache.partial_query visitor, arel.ast, collector
26
+ end
27
+ end
28
+
29
+ # Returns an ActiveRecord::Result instance.
17
30
  def select_all(arel, name = nil, binds = [])
31
+ arel, binds = binds_from_relation arel, binds
18
32
  select(to_sql(arel, binds), name, binds)
19
33
  end
20
34
 
21
35
  # Returns a record hash with the column names as keys and column values
22
36
  # as values.
23
- def select_one(arel, name = nil)
24
- result = select_all(arel, name)
25
- result.first if result
37
+ def select_one(arel, name = nil, binds = [])
38
+ select_all(arel, name, binds).first
26
39
  end
27
40
 
28
41
  # Returns a single value from a record
29
- def select_value(arel, name = nil)
30
- if result = select_one(arel, name)
42
+ def select_value(arel, name = nil, binds = [])
43
+ if result = select_one(arel, name, binds)
31
44
  result.values.first
32
45
  end
33
46
  end
@@ -35,13 +48,13 @@ module ActiveRecord
35
48
  # Returns an array of the values of the first column in a select:
36
49
  # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
37
50
  def select_values(arel, name = nil)
38
- result = select_rows(to_sql(arel, []), name)
39
- result.map { |v| v[0] }
51
+ arel, binds = binds_from_relation arel, []
52
+ select_rows(to_sql(arel, binds), name, binds).map(&:first)
40
53
  end
41
54
 
42
55
  # Returns an array of arrays containing the field values.
43
56
  # Order is the same as that returned by +columns+.
44
- def select_rows(sql, name = nil)
57
+ def select_rows(sql, name = nil, binds = [])
45
58
  end
46
59
  undef_method :select_rows
47
60
 
@@ -57,21 +70,26 @@ module ActiveRecord
57
70
  end
58
71
 
59
72
  # Executes insert +sql+ statement in the context of this connection using
60
- # +binds+ as the bind substitutes. +name+ is the logged along with
73
+ # +binds+ as the bind substitutes. +name+ is logged along with
61
74
  # the executed +sql+ statement.
62
- def exec_insert(sql, name, binds)
75
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
63
76
  exec_query(sql, name, binds)
64
77
  end
65
78
 
66
79
  # Executes delete +sql+ statement in the context of this connection using
67
- # +binds+ as the bind substitutes. +name+ is the logged along with
80
+ # +binds+ as the bind substitutes. +name+ is logged along with
68
81
  # the executed +sql+ statement.
69
82
  def exec_delete(sql, name, binds)
70
83
  exec_query(sql, name, binds)
71
84
  end
72
85
 
86
+ # Executes the truncate statement.
87
+ def truncate(table_name, name = nil)
88
+ raise NotImplementedError
89
+ end
90
+
73
91
  # Executes update +sql+ statement in the context of this connection using
74
- # +binds+ as the bind substitutes. +name+ is the logged along with
92
+ # +binds+ as the bind substitutes. +name+ is logged along with
75
93
  # the executed +sql+ statement.
76
94
  def exec_update(sql, name, binds)
77
95
  exec_query(sql, name, binds)
@@ -87,7 +105,7 @@ module ActiveRecord
87
105
  # passed in as +id_value+.
88
106
  def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
89
107
  sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
90
- value = exec_insert(sql, name, binds)
108
+ value = exec_insert(sql, name, binds, pk, sequence_name)
91
109
  id_value || last_inserted_id(value)
92
110
  end
93
111
 
@@ -101,20 +119,6 @@ module ActiveRecord
101
119
  exec_delete(to_sql(arel, binds), name, binds)
102
120
  end
103
121
 
104
- # Checks whether there is currently no transaction active. This is done
105
- # by querying the database driver, and does not use the transaction
106
- # house-keeping information recorded by #increment_open_transactions and
107
- # friends.
108
- #
109
- # Returns true if there is no transaction active, false if there is a
110
- # transaction active, and nil if this information is unknown.
111
- #
112
- # Not all adapters supports transaction state introspection. Currently,
113
- # only the PostgreSQL adapter supports this.
114
- def outside_transaction?
115
- nil
116
- end
117
-
118
122
  # Returns +true+ when the connection adapter supports prepared statement
119
123
  # caching, otherwise returns +false+
120
124
  def supports_statement_cache?
@@ -133,7 +137,8 @@ module ActiveRecord
133
137
  # In order to get around this problem, #transaction will emulate the effect
134
138
  # of nested transactions, by using savepoints:
135
139
  # http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
136
- # Savepoints are supported by MySQL and PostgreSQL, but not SQLite3.
140
+ # Savepoints are supported by MySQL and PostgreSQL. SQLite3 version >= '3.6.8'
141
+ # supports savepoints.
137
142
  #
138
143
  # It is safe to call this method if a database transaction is already open,
139
144
  # i.e. if #transaction is called within another #transaction block. In case
@@ -158,101 +163,117 @@ module ActiveRecord
158
163
  # already-automatically-released savepoints:
159
164
  #
160
165
  # Model.connection.transaction do # BEGIN
161
- # Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
166
+ # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
162
167
  # Model.connection.create_table(...)
163
168
  # # active_record_1 now automatically released
164
169
  # end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
165
170
  # end
171
+ #
172
+ # == Transaction isolation
173
+ #
174
+ # If your database supports setting the isolation level for a transaction, you can set
175
+ # it like so:
176
+ #
177
+ # Post.transaction(isolation: :serializable) do
178
+ # # ...
179
+ # end
180
+ #
181
+ # Valid isolation levels are:
182
+ #
183
+ # * <tt>:read_uncommitted</tt>
184
+ # * <tt>:read_committed</tt>
185
+ # * <tt>:repeatable_read</tt>
186
+ # * <tt>:serializable</tt>
187
+ #
188
+ # You should consult the documentation for your database to understand the
189
+ # semantics of these different levels:
190
+ #
191
+ # * http://www.postgresql.org/docs/9.1/static/transaction-iso.html
192
+ # * https://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
193
+ #
194
+ # An <tt>ActiveRecord::TransactionIsolationError</tt> will be raised if:
195
+ #
196
+ # * The adapter does not support setting the isolation level
197
+ # * You are joining an existing open transaction
198
+ # * You are creating a nested (savepoint) transaction
199
+ #
200
+ # The mysql, mysql2 and postgresql adapters support setting the transaction
201
+ # isolation level. However, support is disabled for MySQL versions below 5,
202
+ # because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
203
+ # which means the isolation level gets persisted outside the transaction.
166
204
  def transaction(options = {})
167
- options.assert_valid_keys :requires_new, :joinable
205
+ options.assert_valid_keys :requires_new, :joinable, :isolation
168
206
 
169
- last_transaction_joinable = defined?(@transaction_joinable) ? @transaction_joinable : nil
170
- if options.has_key?(:joinable)
171
- @transaction_joinable = options[:joinable]
172
- else
173
- @transaction_joinable = true
174
- end
175
- requires_new = options[:requires_new] || !last_transaction_joinable
176
-
177
- transaction_open = false
178
- @_current_transaction_records ||= []
179
-
180
- begin
181
- if block_given?
182
- if requires_new || open_transactions == 0
183
- if open_transactions == 0
184
- begin_db_transaction
185
- elsif requires_new
186
- create_savepoint
187
- end
188
- increment_open_transactions
189
- transaction_open = true
190
- @_current_transaction_records.push([])
191
- end
192
- yield
193
- end
194
- rescue Exception => database_transaction_rollback
195
- if transaction_open && !outside_transaction?
196
- transaction_open = false
197
- decrement_open_transactions
198
- if open_transactions == 0
199
- rollback_db_transaction
200
- rollback_transaction_records(true)
201
- else
202
- rollback_to_savepoint
203
- rollback_transaction_records(false)
204
- end
205
- end
206
- raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
207
- end
208
- ensure
209
- @transaction_joinable = last_transaction_joinable
210
-
211
- if outside_transaction?
212
- @open_transactions = 0
213
- elsif transaction_open
214
- decrement_open_transactions
215
- begin
216
- if open_transactions == 0
217
- commit_db_transaction
218
- commit_transaction_records
219
- else
220
- release_savepoint
221
- save_point_records = @_current_transaction_records.pop
222
- unless save_point_records.blank?
223
- @_current_transaction_records.push([]) if @_current_transaction_records.empty?
224
- @_current_transaction_records.last.concat(save_point_records)
225
- end
226
- end
227
- rescue Exception => database_transaction_rollback
228
- if open_transactions == 0
229
- rollback_db_transaction
230
- rollback_transaction_records(true)
231
- else
232
- rollback_to_savepoint
233
- rollback_transaction_records(false)
234
- end
235
- raise
207
+ if !options[:requires_new] && current_transaction.joinable?
208
+ if options[:isolation]
209
+ raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
236
210
  end
211
+ yield
212
+ else
213
+ transaction_manager.within_new_transaction(options) { yield }
237
214
  end
215
+ rescue ActiveRecord::Rollback
216
+ # rollbacks are silently swallowed
217
+ end
218
+
219
+ attr_reader :transaction_manager #:nodoc:
220
+
221
+ delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction, :commit_transaction, :rollback_transaction, to: :transaction_manager
222
+
223
+ def transaction_open?
224
+ current_transaction.open?
225
+ end
226
+
227
+ def reset_transaction #:nodoc:
228
+ @transaction_manager = TransactionManager.new(self)
238
229
  end
239
230
 
240
231
  # Register a record with the current transaction so that its after_commit and after_rollback callbacks
241
232
  # can be called.
242
233
  def add_transaction_record(record)
243
- last_batch = @_current_transaction_records.last
244
- last_batch << record if last_batch
234
+ current_transaction.add_record(record)
235
+ end
236
+
237
+ def transaction_state
238
+ current_transaction.state
245
239
  end
246
240
 
247
241
  # Begins the transaction (and turns off auto-committing).
248
242
  def begin_db_transaction() end
249
243
 
244
+ def transaction_isolation_levels
245
+ {
246
+ read_uncommitted: "READ UNCOMMITTED",
247
+ read_committed: "READ COMMITTED",
248
+ repeatable_read: "REPEATABLE READ",
249
+ serializable: "SERIALIZABLE"
250
+ }
251
+ end
252
+
253
+ # Begins the transaction with the isolation level set. Raises an error by
254
+ # default; adapters that support setting the isolation level should implement
255
+ # this method.
256
+ def begin_isolated_db_transaction(isolation)
257
+ raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
258
+ end
259
+
250
260
  # Commits the transaction (and turns on auto-committing).
251
261
  def commit_db_transaction() end
252
262
 
253
263
  # Rolls back the transaction (and turns on auto-committing). Must be
254
264
  # done if the transaction block raises an exception or returns false.
255
- def rollback_db_transaction() end
265
+ def rollback_db_transaction
266
+ exec_rollback_db_transaction
267
+ end
268
+
269
+ def exec_rollback_db_transaction() end #:nodoc:
270
+
271
+ def rollback_to_savepoint(name = nil)
272
+ exec_rollback_to_savepoint(name)
273
+ end
274
+
275
+ def exec_rollback_to_savepoint(name = nil) #:nodoc:
276
+ end
256
277
 
257
278
  def default_sequence_name(table, column)
258
279
  nil
@@ -266,27 +287,24 @@ module ActiveRecord
266
287
  # Inserts the given fixture into the table. Overridden in adapters that require
267
288
  # something beyond a simple insert (eg. Oracle).
268
289
  def insert_fixture(fixture, table_name)
290
+ fixture = fixture.stringify_keys
269
291
  columns = schema_cache.columns_hash(table_name)
270
292
 
271
293
  key_list = []
272
294
  value_list = fixture.map do |name, value|
273
- key_list << quote_column_name(name)
274
- quote(value, columns[name])
295
+ if column = columns[name]
296
+ key_list << quote_column_name(name)
297
+ quote(value, column)
298
+ else
299
+ raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.)
300
+ end
275
301
  end
276
302
 
277
303
  execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert'
278
304
  end
279
305
 
280
306
  def empty_insert_statement_value
281
- "VALUES(DEFAULT)"
282
- end
283
-
284
- def case_sensitive_equality_operator
285
- "="
286
- end
287
-
288
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
289
- "WHERE #{quoted_primary_key} IN (SELECT #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
307
+ "DEFAULT VALUES"
290
308
  end
291
309
 
292
310
  # Sanitizes the given LIMIT parameter in order to prevent SQL injection.
@@ -301,7 +319,7 @@ module ActiveRecord
301
319
  def sanitize_limit(limit)
302
320
  if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
303
321
  limit
304
- elsif limit.to_s =~ /,/
322
+ elsif limit.to_s.include?(',')
305
323
  Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
306
324
  else
307
325
  Integer(limit)
@@ -309,21 +327,35 @@ module ActiveRecord
309
327
  end
310
328
 
311
329
  # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
312
- # on mysql (even when aliasing the tables), but mysql allows using JOIN directly in
313
- # an UPDATE statement, so in the mysql adapters we redefine this to do that.
330
+ # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in
331
+ # an UPDATE statement, so in the MySQL adapters we redefine this to do that.
314
332
  def join_to_update(update, select) #:nodoc:
315
- subselect = select.clone
316
- subselect.projections = [update.key]
333
+ key = update.key
334
+ subselect = subquery_for(key, select)
317
335
 
318
- update.where update.key.in(subselect)
336
+ update.where key.in(subselect)
337
+ end
338
+
339
+ def join_to_delete(delete, select, key) #:nodoc:
340
+ subselect = subquery_for(key, select)
341
+
342
+ delete.where key.in(subselect)
319
343
  end
320
344
 
321
345
  protected
322
- # Returns an array of record hashes with the column names as keys and
323
- # column values as values.
346
+
347
+ # Returns a subquery for the given key using the join information.
348
+ def subquery_for(key, select)
349
+ subselect = select.clone
350
+ subselect.projections = [key]
351
+ subselect
352
+ end
353
+
354
+ # Returns an ActiveRecord::Result instance.
324
355
  def select(sql, name = nil, binds = [])
356
+ exec_query(sql, name, binds)
325
357
  end
326
- undef_method :select
358
+
327
359
 
328
360
  # Returns the last auto-generated ID from the affected table.
329
361
  def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
@@ -341,50 +373,21 @@ module ActiveRecord
341
373
  update_sql(sql, name)
342
374
  end
343
375
 
344
- # Send a rollback message to all records after they have been rolled back. If rollback
345
- # is false, only rollback records since the last save point.
346
- def rollback_transaction_records(rollback)
347
- if rollback
348
- records = @_current_transaction_records.flatten
349
- @_current_transaction_records.clear
350
- else
351
- records = @_current_transaction_records.pop
352
- end
376
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
377
+ [sql, binds]
378
+ end
353
379
 
354
- unless records.blank?
355
- records.uniq.each do |record|
356
- begin
357
- record.rolledback!(rollback)
358
- rescue Exception => e
359
- record.logger.error(e) if record.respond_to?(:logger) && record.logger
360
- end
361
- end
362
- end
380
+ def last_inserted_id(result)
381
+ row = result.rows.first
382
+ row && row.first
363
383
  end
364
384
 
365
- # Send a commit message to all records after they have been committed.
366
- def commit_transaction_records
367
- records = @_current_transaction_records.flatten
368
- @_current_transaction_records.clear
369
- unless records.blank?
370
- records.uniq.each do |record|
371
- begin
372
- record.committed!
373
- rescue Exception => e
374
- record.logger.error(e) if record.respond_to?(:logger) && record.logger
375
- end
376
- end
385
+ def binds_from_relation(relation, binds)
386
+ if relation.is_a?(Relation) && binds.empty?
387
+ relation, binds = relation.arel, relation.bind_values
377
388
  end
389
+ [relation, binds]
378
390
  end
379
-
380
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
381
- [sql, binds]
382
- end
383
-
384
- def last_inserted_id(result)
385
- row = result.rows.first
386
- row && row.first
387
- end
388
391
  end
389
392
  end
390
393
  end
@@ -2,17 +2,17 @@ module ActiveRecord
2
2
  module ConnectionAdapters # :nodoc:
3
3
  module QueryCache
4
4
  class << self
5
- def included(base)
6
- dirties_query_cache base, :insert, :update, :delete
5
+ def included(base) #:nodoc:
6
+ dirties_query_cache base, :insert, :update, :delete, :rollback_to_savepoint, :rollback_db_transaction
7
7
  end
8
8
 
9
9
  def dirties_query_cache(base, *method_names)
10
10
  method_names.each do |method_name|
11
11
  base.class_eval <<-end_code, __FILE__, __LINE__ + 1
12
- def #{method_name}(*) # def update_with_query_dirty(*args)
13
- clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
14
- super # update_without_query_dirty(*args)
15
- end # end
12
+ def #{method_name}(*)
13
+ clear_query_cache if @query_cache_enabled
14
+ super
15
+ end
16
16
  end_code
17
17
  end
18
18
  end
@@ -20,13 +20,19 @@ module ActiveRecord
20
20
 
21
21
  attr_reader :query_cache, :query_cache_enabled
22
22
 
23
+ def initialize(*)
24
+ super
25
+ @query_cache = Hash.new { |h,sql| h[sql] = {} }
26
+ @query_cache_enabled = false
27
+ end
28
+
23
29
  # Enable the query cache within the block.
24
30
  def cache
25
31
  old, @query_cache_enabled = @query_cache_enabled, true
26
32
  yield
27
33
  ensure
28
- clear_query_cache
29
34
  @query_cache_enabled = old
35
+ clear_query_cache unless @query_cache_enabled
30
36
  end
31
37
 
32
38
  def enable_query_cache!
@@ -57,6 +63,7 @@ module ActiveRecord
57
63
 
58
64
  def select_all(arel, name = nil, binds = [])
59
65
  if @query_cache_enabled && !locked?(arel)
66
+ arel, binds = binds_from_relation arel, binds
60
67
  sql = to_sql(arel, binds)
61
68
  cache_sql(sql, binds) { super(sql, name, binds) }
62
69
  else
@@ -65,26 +72,24 @@ module ActiveRecord
65
72
  end
66
73
 
67
74
  private
68
- def cache_sql(sql, binds)
69
- result =
70
- if @query_cache[sql].key?(binds)
71
- ActiveSupport::Notifications.instrument("sql.active_record",
72
- :sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id)
73
- @query_cache[sql][binds]
74
- else
75
- @query_cache[sql][binds] = yield
76
- end
77
-
78
- result.collect { |row| row.dup }
79
- end
80
75
 
81
- def locked?(arel)
82
- if arel.respond_to?(:locked)
83
- arel.locked
76
+ def cache_sql(sql, binds)
77
+ result =
78
+ if @query_cache[sql].key?(binds)
79
+ ActiveSupport::Notifications.instrument("sql.active_record",
80
+ :sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id)
81
+ @query_cache[sql][binds]
84
82
  else
85
- false
83
+ @query_cache[sql][binds] = yield
86
84
  end
87
- end
85
+ result.dup
86
+ end
87
+
88
+ # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such
89
+ # queries should not be cached.
90
+ def locked?(arel)
91
+ arel.respond_to?(:locked) && arel.locked
92
+ end
88
93
  end
89
94
  end
90
95
  end