activerecord 3.1.10 → 4.2.11

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 (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  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 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  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 -102
  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 +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  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 +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  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 +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  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 +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  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 +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  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 +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  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 +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  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 +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  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 +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  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 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  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 +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -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,35 +1,46 @@
1
- require 'active_support/core_ext/module/deprecation'
2
-
3
1
  module ActiveRecord
4
2
  module ConnectionAdapters # :nodoc:
5
3
  module DatabaseStatements
4
+ def initialize
5
+ super
6
+ reset_transaction
7
+ end
8
+
6
9
  # Converts an arel AST to SQL
7
10
  def to_sql(arel, binds = [])
8
11
  if arel.respond_to?(:ast)
9
- visitor.accept(arel.ast) do
10
- quote(*binds.shift.reverse)
11
- end
12
+ collected = visitor.accept(arel.ast, collector)
13
+ collected.compile(binds.dup, self)
12
14
  else
13
15
  arel
14
16
  end
15
17
  end
16
18
 
17
- # Returns an array of record hashes with the column names as keys and
18
- # 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.
19
30
  def select_all(arel, name = nil, binds = [])
31
+ arel, binds = binds_from_relation arel, binds
20
32
  select(to_sql(arel, binds), name, binds)
21
33
  end
22
34
 
23
35
  # Returns a record hash with the column names as keys and column values
24
36
  # as values.
25
- def select_one(arel, name = nil)
26
- result = select_all(arel, name)
27
- result.first if result
37
+ def select_one(arel, name = nil, binds = [])
38
+ select_all(arel, name, binds).first
28
39
  end
29
40
 
30
41
  # Returns a single value from a record
31
- def select_value(arel, name = nil)
32
- if result = select_one(arel, name)
42
+ def select_value(arel, name = nil, binds = [])
43
+ if result = select_one(arel, name, binds)
33
44
  result.values.first
34
45
  end
35
46
  end
@@ -37,13 +48,13 @@ module ActiveRecord
37
48
  # Returns an array of the values of the first column in a select:
38
49
  # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
39
50
  def select_values(arel, name = nil)
40
- result = select_rows(to_sql(arel, []), name)
41
- result.map { |v| v[0] }
51
+ arel, binds = binds_from_relation arel, []
52
+ select_rows(to_sql(arel, binds), name, binds).map(&:first)
42
53
  end
43
54
 
44
55
  # Returns an array of arrays containing the field values.
45
56
  # Order is the same as that returned by +columns+.
46
- def select_rows(sql, name = nil)
57
+ def select_rows(sql, name = nil, binds = [])
47
58
  end
48
59
  undef_method :select_rows
49
60
 
@@ -53,27 +64,32 @@ module ActiveRecord
53
64
  undef_method :execute
54
65
 
55
66
  # Executes +sql+ statement in the context of this connection using
56
- # +binds+ as the bind substitutes. +name+ is logged along with
67
+ # +binds+ as the bind substitutes. +name+ is logged along with
57
68
  # the executed +sql+ statement.
58
69
  def exec_query(sql, name = 'SQL', binds = [])
59
70
  end
60
71
 
61
72
  # Executes insert +sql+ statement in the context of this connection using
62
- # +binds+ as the bind substitutes. +name+ is the logged along with
73
+ # +binds+ as the bind substitutes. +name+ is logged along with
63
74
  # the executed +sql+ statement.
64
- def exec_insert(sql, name, binds)
75
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
65
76
  exec_query(sql, name, binds)
66
77
  end
67
78
 
68
79
  # Executes delete +sql+ statement in the context of this connection using
69
- # +binds+ as the bind substitutes. +name+ is the logged along with
80
+ # +binds+ as the bind substitutes. +name+ is logged along with
70
81
  # the executed +sql+ statement.
71
82
  def exec_delete(sql, name, binds)
72
83
  exec_query(sql, name, binds)
73
84
  end
74
85
 
86
+ # Executes the truncate statement.
87
+ def truncate(table_name, name = nil)
88
+ raise NotImplementedError
89
+ end
90
+
75
91
  # Executes update +sql+ statement in the context of this connection using
76
- # +binds+ as the bind substitutes. +name+ is the logged along with
92
+ # +binds+ as the bind substitutes. +name+ is logged along with
77
93
  # the executed +sql+ statement.
78
94
  def exec_update(sql, name, binds)
79
95
  exec_query(sql, name, binds)
@@ -89,7 +105,7 @@ module ActiveRecord
89
105
  # passed in as +id_value+.
90
106
  def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
91
107
  sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
92
- value = exec_insert(sql, name, binds)
108
+ value = exec_insert(sql, name, binds, pk, sequence_name)
93
109
  id_value || last_inserted_id(value)
94
110
  end
95
111
 
@@ -103,20 +119,6 @@ module ActiveRecord
103
119
  exec_delete(to_sql(arel, binds), name, binds)
104
120
  end
105
121
 
106
- # Checks whether there is currently no transaction active. This is done
107
- # by querying the database driver, and does not use the transaction
108
- # house-keeping information recorded by #increment_open_transactions and
109
- # friends.
110
- #
111
- # Returns true if there is no transaction active, false if there is a
112
- # transaction active, and nil if this information is unknown.
113
- #
114
- # Not all adapters supports transaction state introspection. Currently,
115
- # only the PostgreSQL adapter supports this.
116
- def outside_transaction?
117
- nil
118
- end
119
-
120
122
  # Returns +true+ when the connection adapter supports prepared statement
121
123
  # caching, otherwise returns +false+
122
124
  def supports_statement_cache?
@@ -135,7 +137,8 @@ module ActiveRecord
135
137
  # In order to get around this problem, #transaction will emulate the effect
136
138
  # of nested transactions, by using savepoints:
137
139
  # http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
138
- # 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.
139
142
  #
140
143
  # It is safe to call this method if a database transaction is already open,
141
144
  # i.e. if #transaction is called within another #transaction block. In case
@@ -160,126 +163,117 @@ module ActiveRecord
160
163
  # already-automatically-released savepoints:
161
164
  #
162
165
  # Model.connection.transaction do # BEGIN
163
- # 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
164
167
  # Model.connection.create_table(...)
165
168
  # # active_record_1 now automatically released
166
169
  # end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
167
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.
168
204
  def transaction(options = {})
169
- options.assert_valid_keys :requires_new, :joinable
205
+ options.assert_valid_keys :requires_new, :joinable, :isolation
170
206
 
171
- last_transaction_joinable = defined?(@transaction_joinable) ? @transaction_joinable : nil
172
- if options.has_key?(:joinable)
173
- @transaction_joinable = options[:joinable]
174
- else
175
- @transaction_joinable = true
176
- end
177
- requires_new = options[:requires_new] || !last_transaction_joinable
178
-
179
- transaction_open = false
180
- @_current_transaction_records ||= []
181
-
182
- begin
183
- if block_given?
184
- if requires_new || open_transactions == 0
185
- if open_transactions == 0
186
- begin_db_transaction
187
- elsif requires_new
188
- create_savepoint
189
- end
190
- increment_open_transactions
191
- transaction_open = true
192
- @_current_transaction_records.push([])
193
- end
194
- yield
195
- end
196
- rescue Exception => database_transaction_rollback
197
- if transaction_open && !outside_transaction?
198
- transaction_open = false
199
- decrement_open_transactions
200
- if open_transactions == 0
201
- rollback_db_transaction
202
- rollback_transaction_records(true)
203
- else
204
- rollback_to_savepoint
205
- rollback_transaction_records(false)
206
- end
207
- end
208
- raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
209
- end
210
- ensure
211
- @transaction_joinable = last_transaction_joinable
212
-
213
- if outside_transaction?
214
- @open_transactions = 0
215
- elsif transaction_open
216
- decrement_open_transactions
217
- begin
218
- if open_transactions == 0
219
- commit_db_transaction
220
- commit_transaction_records
221
- else
222
- release_savepoint
223
- save_point_records = @_current_transaction_records.pop
224
- unless save_point_records.blank?
225
- @_current_transaction_records.push([]) if @_current_transaction_records.empty?
226
- @_current_transaction_records.last.concat(save_point_records)
227
- end
228
- end
229
- rescue Exception => database_transaction_rollback
230
- if open_transactions == 0
231
- rollback_db_transaction
232
- rollback_transaction_records(true)
233
- else
234
- rollback_to_savepoint
235
- rollback_transaction_records(false)
236
- end
237
- raise
207
+ if !options[:requires_new] && current_transaction.joinable?
208
+ if options[:isolation]
209
+ raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
238
210
  end
211
+ yield
212
+ else
213
+ transaction_manager.within_new_transaction(options) { yield }
239
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)
240
229
  end
241
230
 
242
231
  # Register a record with the current transaction so that its after_commit and after_rollback callbacks
243
232
  # can be called.
244
233
  def add_transaction_record(record)
245
- last_batch = @_current_transaction_records.last
246
- last_batch << record if last_batch
234
+ current_transaction.add_record(record)
235
+ end
236
+
237
+ def transaction_state
238
+ current_transaction.state
247
239
  end
248
240
 
249
241
  # Begins the transaction (and turns off auto-committing).
250
242
  def begin_db_transaction() end
251
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
+
252
260
  # Commits the transaction (and turns on auto-committing).
253
261
  def commit_db_transaction() end
254
262
 
255
263
  # Rolls back the transaction (and turns on auto-committing). Must be
256
264
  # done if the transaction block raises an exception or returns false.
257
- def rollback_db_transaction() end
265
+ def rollback_db_transaction
266
+ exec_rollback_db_transaction
267
+ end
258
268
 
259
- # Appends +LIMIT+ and +OFFSET+ options to an SQL statement, or some SQL
260
- # fragment that has the same semantics as LIMIT and OFFSET.
261
- #
262
- # +options+ must be a Hash which contains a +:limit+ option
263
- # and an +:offset+ option.
264
- #
265
- # This method *modifies* the +sql+ parameter.
266
- #
267
- # This method is deprecated!! Stop using it!
268
- #
269
- # ===== Examples
270
- # add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
271
- # generates
272
- # SELECT * FROM suppliers LIMIT 10 OFFSET 50
273
- def add_limit_offset!(sql, options)
274
- if limit = options[:limit]
275
- sql << " LIMIT #{sanitize_limit(limit)}"
276
- end
277
- if offset = options[:offset]
278
- sql << " OFFSET #{offset.to_i}"
279
- end
280
- sql
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:
281
276
  end
282
- deprecate :add_limit_offset!
283
277
 
284
278
  def default_sequence_name(table, column)
285
279
  nil
@@ -287,48 +281,45 @@ module ActiveRecord
287
281
 
288
282
  # Set the sequence to the max value of the table's column.
289
283
  def reset_sequence!(table, column, sequence = nil)
290
- # Do nothing by default. Implement for PostgreSQL, Oracle, ...
284
+ # Do nothing by default. Implement for PostgreSQL, Oracle, ...
291
285
  end
292
286
 
293
287
  # Inserts the given fixture into the table. Overridden in adapters that require
294
288
  # something beyond a simple insert (eg. Oracle).
295
289
  def insert_fixture(fixture, table_name)
296
- columns = Hash[columns(table_name).map { |c| [c.name, c] }]
290
+ fixture = fixture.stringify_keys
291
+ columns = schema_cache.columns_hash(table_name)
297
292
 
298
293
  key_list = []
299
294
  value_list = fixture.map do |name, value|
300
- key_list << quote_column_name(name)
301
- 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
302
301
  end
303
302
 
304
303
  execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert'
305
304
  end
306
305
 
307
306
  def empty_insert_statement_value
308
- "VALUES(DEFAULT)"
309
- end
310
-
311
- def case_sensitive_equality_operator
312
- "="
313
- end
314
-
315
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
316
- "WHERE #{quoted_primary_key} IN (SELECT #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
307
+ "DEFAULT VALUES"
317
308
  end
318
309
 
319
310
  # Sanitizes the given LIMIT parameter in order to prevent SQL injection.
320
311
  #
321
312
  # The +limit+ may be anything that can evaluate to a string via #to_s. It
322
- # should look like an integer, or a comma-delimited list of integers, or
313
+ # should look like an integer, or a comma-delimited list of integers, or
323
314
  # an Arel SQL literal.
324
315
  #
325
- # Returns Integer and Arel::Nodes::SqlLiteral limits as is.
316
+ # Returns Integer and Arel::Nodes::SqlLiteral limits as is.
326
317
  # Returns the sanitized limit parameter, either as an integer, or as a
327
318
  # string which contains a comma-delimited list of integers.
328
319
  def sanitize_limit(limit)
329
320
  if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
330
321
  limit
331
- elsif limit.to_s =~ /,/
322
+ elsif limit.to_s.include?(',')
332
323
  Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
333
324
  else
334
325
  Integer(limit)
@@ -336,21 +327,35 @@ module ActiveRecord
336
327
  end
337
328
 
338
329
  # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
339
- # on mysql (even when aliasing the tables), but mysql allows using JOIN directly in
340
- # 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.
341
332
  def join_to_update(update, select) #:nodoc:
342
- subselect = select.clone
343
- subselect.projections = [update.key]
333
+ key = update.key
334
+ subselect = subquery_for(key, select)
344
335
 
345
- 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)
346
343
  end
347
344
 
348
345
  protected
349
- # Returns an array of record hashes with the column names as keys and
350
- # 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.
351
355
  def select(sql, name = nil, binds = [])
356
+ exec_query(sql, name, binds)
352
357
  end
353
- undef_method :select
358
+
354
359
 
355
360
  # Returns the last auto-generated ID from the affected table.
356
361
  def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
@@ -368,50 +373,21 @@ module ActiveRecord
368
373
  update_sql(sql, name)
369
374
  end
370
375
 
371
- # Send a rollback message to all records after they have been rolled back. If rollback
372
- # is false, only rollback records since the last save point.
373
- def rollback_transaction_records(rollback) #:nodoc
374
- if rollback
375
- records = @_current_transaction_records.flatten
376
- @_current_transaction_records.clear
377
- else
378
- records = @_current_transaction_records.pop
379
- end
376
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
377
+ [sql, binds]
378
+ end
380
379
 
381
- unless records.blank?
382
- records.uniq.each do |record|
383
- begin
384
- record.rolledback!(rollback)
385
- rescue Exception => e
386
- record.logger.error(e) if record.respond_to?(:logger) && record.logger
387
- end
388
- end
389
- end
380
+ def last_inserted_id(result)
381
+ row = result.rows.first
382
+ row && row.first
390
383
  end
391
384
 
392
- # Send a commit message to all records after they have been committed.
393
- def commit_transaction_records #:nodoc
394
- records = @_current_transaction_records.flatten
395
- @_current_transaction_records.clear
396
- unless records.blank?
397
- records.uniq.each do |record|
398
- begin
399
- record.committed!
400
- rescue Exception => e
401
- record.logger.error(e) if record.respond_to?(:logger) && record.logger
402
- end
403
- end
385
+ def binds_from_relation(relation, binds)
386
+ if relation.is_a?(Relation) && binds.empty?
387
+ relation, binds = relation.arel, relation.bind_values
404
388
  end
389
+ [relation, binds]
405
390
  end
406
-
407
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
408
- [sql, binds]
409
- end
410
-
411
- def last_inserted_id(result)
412
- row = result.rows.first
413
- row && row.first
414
- end
415
391
  end
416
392
  end
417
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!
@@ -56,7 +62,8 @@ module ActiveRecord
56
62
  end
57
63
 
58
64
  def select_all(arel, name = nil, binds = [])
59
- if @query_cache_enabled
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,18 +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, :name => "CACHE", :connection_id => object_id)
73
- @query_cache[sql][binds]
74
- else
75
- @query_cache[sql][binds] = yield
76
- end
77
75
 
78
- result.collect { |row| row.dup }
79
- end
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]
82
+ else
83
+ @query_cache[sql][binds] = yield
84
+ 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
80
93
  end
81
94
  end
82
95
  end