activerecord 4.1.0 → 4.2.0

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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +776 -1330
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +12 -8
  5. data/lib/active_record/association_relation.rb +4 -0
  6. data/lib/active_record/associations/alias_tracker.rb +14 -13
  7. data/lib/active_record/associations/association.rb +2 -2
  8. data/lib/active_record/associations/association_scope.rb +83 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +15 -4
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -6
  13. data/lib/active_record/associations/builder/has_many.rb +1 -1
  14. data/lib/active_record/associations/builder/has_one.rb +2 -2
  15. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  16. data/lib/active_record/associations/collection_association.rb +66 -29
  17. data/lib/active_record/associations/collection_proxy.rb +22 -26
  18. data/lib/active_record/associations/has_many_association.rb +65 -18
  19. data/lib/active_record/associations/has_many_through_association.rb +55 -27
  20. data/lib/active_record/associations/has_one_association.rb +0 -1
  21. data/lib/active_record/associations/join_dependency/join_association.rb +19 -15
  22. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  23. data/lib/active_record/associations/join_dependency.rb +20 -12
  24. data/lib/active_record/associations/preloader/association.rb +34 -11
  25. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  26. data/lib/active_record/associations/preloader.rb +49 -59
  27. data/lib/active_record/associations/singular_association.rb +25 -4
  28. data/lib/active_record/associations/through_association.rb +23 -14
  29. data/lib/active_record/associations.rb +171 -42
  30. data/lib/active_record/attribute.rb +149 -0
  31. data/lib/active_record/attribute_assignment.rb +18 -10
  32. data/lib/active_record/attribute_decorators.rb +66 -0
  33. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  34. data/lib/active_record/attribute_methods/dirty.rb +98 -44
  35. data/lib/active_record/attribute_methods/primary_key.rb +14 -8
  36. data/lib/active_record/attribute_methods/query.rb +1 -1
  37. data/lib/active_record/attribute_methods/read.rb +22 -59
  38. data/lib/active_record/attribute_methods/serialization.rb +37 -147
  39. data/lib/active_record/attribute_methods/time_zone_conversion.rb +34 -28
  40. data/lib/active_record/attribute_methods/write.rb +14 -21
  41. data/lib/active_record/attribute_methods.rb +67 -94
  42. data/lib/active_record/attribute_set/builder.rb +86 -0
  43. data/lib/active_record/attribute_set.rb +77 -0
  44. data/lib/active_record/attributes.rb +139 -0
  45. data/lib/active_record/autosave_association.rb +45 -38
  46. data/lib/active_record/base.rb +10 -20
  47. data/lib/active_record/callbacks.rb +7 -7
  48. data/lib/active_record/coders/json.rb +13 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +78 -52
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +38 -59
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -55
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -5
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +126 -54
  55. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +198 -64
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +126 -114
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -55
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +240 -135
  60. data/lib/active_record/connection_adapters/column.rb +28 -239
  61. data/lib/active_record/connection_adapters/connection_specification.rb +16 -25
  62. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -22
  63. data/lib/active_record/connection_adapters/mysql_adapter.rb +65 -149
  64. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  65. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  66. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -27
  67. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -374
  93. data/lib/active_record/connection_adapters/postgresql/quoting.rb +55 -135
  94. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  96. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +127 -38
  97. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  98. data/lib/active_record/connection_adapters/postgresql_adapter.rb +220 -466
  99. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  100. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -61
  101. data/lib/active_record/connection_handling.rb +3 -3
  102. data/lib/active_record/core.rb +143 -32
  103. data/lib/active_record/counter_cache.rb +60 -7
  104. data/lib/active_record/enum.rb +10 -11
  105. data/lib/active_record/errors.rb +49 -27
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/fixtures.rb +56 -70
  108. data/lib/active_record/gem_version.rb +2 -2
  109. data/lib/active_record/inheritance.rb +35 -10
  110. data/lib/active_record/integration.rb +4 -4
  111. data/lib/active_record/locking/optimistic.rb +35 -17
  112. data/lib/active_record/log_subscriber.rb +1 -1
  113. data/lib/active_record/migration/command_recorder.rb +19 -2
  114. data/lib/active_record/migration/join_table.rb +1 -1
  115. data/lib/active_record/migration.rb +52 -49
  116. data/lib/active_record/model_schema.rb +49 -57
  117. data/lib/active_record/nested_attributes.rb +7 -7
  118. data/lib/active_record/null_relation.rb +19 -5
  119. data/lib/active_record/persistence.rb +50 -31
  120. data/lib/active_record/query_cache.rb +3 -3
  121. data/lib/active_record/querying.rb +10 -7
  122. data/lib/active_record/railtie.rb +14 -11
  123. data/lib/active_record/railties/databases.rake +56 -54
  124. data/lib/active_record/readonly_attributes.rb +0 -1
  125. data/lib/active_record/reflection.rb +286 -102
  126. data/lib/active_record/relation/batches.rb +0 -1
  127. data/lib/active_record/relation/calculations.rb +39 -31
  128. data/lib/active_record/relation/delegation.rb +2 -2
  129. data/lib/active_record/relation/finder_methods.rb +80 -36
  130. data/lib/active_record/relation/merger.rb +25 -30
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +31 -13
  132. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  133. data/lib/active_record/relation/predicate_builder.rb +11 -10
  134. data/lib/active_record/relation/query_methods.rb +141 -55
  135. data/lib/active_record/relation/spawn_methods.rb +3 -0
  136. data/lib/active_record/relation.rb +69 -30
  137. data/lib/active_record/result.rb +18 -7
  138. data/lib/active_record/sanitization.rb +12 -2
  139. data/lib/active_record/schema.rb +0 -1
  140. data/lib/active_record/schema_dumper.rb +58 -26
  141. data/lib/active_record/schema_migration.rb +11 -0
  142. data/lib/active_record/scoping/default.rb +8 -7
  143. data/lib/active_record/scoping/named.rb +4 -0
  144. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  145. data/lib/active_record/statement_cache.rb +95 -10
  146. data/lib/active_record/store.rb +19 -10
  147. data/lib/active_record/tasks/database_tasks.rb +73 -7
  148. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -2
  149. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  151. data/lib/active_record/timestamp.rb +11 -9
  152. data/lib/active_record/transactions.rb +37 -21
  153. data/lib/active_record/type/big_integer.rb +13 -0
  154. data/lib/active_record/type/binary.rb +50 -0
  155. data/lib/active_record/type/boolean.rb +30 -0
  156. data/lib/active_record/type/date.rb +46 -0
  157. data/lib/active_record/type/date_time.rb +43 -0
  158. data/lib/active_record/type/decimal.rb +40 -0
  159. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  160. data/lib/active_record/type/decorator.rb +14 -0
  161. data/lib/active_record/type/float.rb +19 -0
  162. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  163. data/lib/active_record/type/integer.rb +55 -0
  164. data/lib/active_record/type/mutable.rb +16 -0
  165. data/lib/active_record/type/numeric.rb +36 -0
  166. data/lib/active_record/type/serialized.rb +56 -0
  167. data/lib/active_record/type/string.rb +36 -0
  168. data/lib/active_record/type/text.rb +11 -0
  169. data/lib/active_record/type/time.rb +26 -0
  170. data/lib/active_record/type/time_value.rb +38 -0
  171. data/lib/active_record/type/type_map.rb +64 -0
  172. data/lib/active_record/type/unsigned_integer.rb +15 -0
  173. data/lib/active_record/type/value.rb +101 -0
  174. data/lib/active_record/type.rb +23 -0
  175. data/lib/active_record/validations/associated.rb +5 -3
  176. data/lib/active_record/validations/presence.rb +6 -4
  177. data/lib/active_record/validations/uniqueness.rb +11 -17
  178. data/lib/active_record/validations.rb +25 -19
  179. data/lib/active_record.rb +3 -0
  180. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  181. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +4 -1
  182. data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +65 -10
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -2,6 +2,7 @@ require 'thread'
2
2
  require 'thread_safe'
3
3
  require 'monitor'
4
4
  require 'set'
5
+ require 'active_support/core_ext/string/filters'
5
6
 
6
7
  module ActiveRecord
7
8
  # Raised when a connection could not be obtained within the connection
@@ -58,13 +59,11 @@ module ActiveRecord
58
59
  # * +checkout_timeout+: number of seconds to block and wait for a connection
59
60
  # before giving up and raising a timeout error (default 5 seconds).
60
61
  # * +reaping_frequency+: frequency in seconds to periodically run the
61
- # Reaper, which attempts to find and close dead connections, which can
62
- # occur if a programmer forgets to close a connection at the end of a
63
- # thread or a thread dies unexpectedly. (Default nil, which means don't
64
- # run the Reaper).
65
- # * +dead_connection_timeout+: number of seconds from last checkout
66
- # after which the Reaper will consider a connection reapable. (default
67
- # 5 seconds).
62
+ # Reaper, which attempts to find and recover connections from dead
63
+ # threads, which can occur if a programmer forgets to close a
64
+ # connection at the end of a thread or a thread dies unexpectedly.
65
+ # Regardless of this setting, the Reaper will be invoked before every
66
+ # blocking wait. (Default nil, which means don't schedule the Reaper).
68
67
  class ConnectionPool
69
68
  # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
70
69
  # with which it shares a Monitor. But could be a generic Queue.
@@ -222,7 +221,7 @@ module ActiveRecord
222
221
 
223
222
  include MonitorMixin
224
223
 
225
- attr_accessor :automatic_reconnect, :checkout_timeout, :dead_connection_timeout
224
+ attr_accessor :automatic_reconnect, :checkout_timeout
226
225
  attr_reader :spec, :connections, :size, :reaper
227
226
 
228
227
  # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
@@ -236,8 +235,7 @@ module ActiveRecord
236
235
 
237
236
  @spec = spec
238
237
 
239
- @checkout_timeout = spec.config[:checkout_timeout] || 5
240
- @dead_connection_timeout = spec.config[:dead_connection_timeout] || 5
238
+ @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
241
239
  @reaper = Reaper.new self, spec.config[:reaping_frequency]
242
240
  @reaper.run
243
241
 
@@ -361,11 +359,13 @@ module ActiveRecord
361
359
  # calling +checkout+ on this pool.
362
360
  def checkin(conn)
363
361
  synchronize do
364
- conn.run_callbacks :checkin do
362
+ owner = conn.owner
363
+
364
+ conn._run_checkin_callbacks do
365
365
  conn.expire
366
366
  end
367
367
 
368
- release conn
368
+ release owner
369
369
 
370
370
  @available.add conn
371
371
  end
@@ -378,22 +378,28 @@ module ActiveRecord
378
378
  @connections.delete conn
379
379
  @available.delete conn
380
380
 
381
- # FIXME: we might want to store the key on the connection so that removing
382
- # from the reserved hash will be a little easier.
383
- release conn
381
+ release conn.owner
384
382
 
385
383
  @available.add checkout_new_connection if @available.any_waiting?
386
384
  end
387
385
  end
388
386
 
389
- # Removes dead connections from the pool. A dead connection can occur
390
- # if a programmer forgets to close a connection at the end of a thread
387
+ # Recover lost connections for the pool. A lost connection can occur if
388
+ # a programmer forgets to checkin a connection at the end of a thread
391
389
  # or a thread dies unexpectedly.
392
390
  def reap
393
- synchronize do
394
- stale = Time.now - @dead_connection_timeout
395
- connections.dup.each do |conn|
396
- if conn.in_use? && stale > conn.last_use && !conn.active_threadsafe?
391
+ stale_connections = synchronize do
392
+ @connections.select do |conn|
393
+ conn.in_use? && !conn.owner.alive?
394
+ end
395
+ end
396
+
397
+ stale_connections.each do |conn|
398
+ synchronize do
399
+ if conn.active?
400
+ conn.reset!
401
+ checkin conn
402
+ else
397
403
  remove conn
398
404
  end
399
405
  end
@@ -415,20 +421,15 @@ module ActiveRecord
415
421
  elsif @connections.size < @size
416
422
  checkout_new_connection
417
423
  else
424
+ reap
418
425
  @available.poll(@checkout_timeout)
419
426
  end
420
427
  end
421
428
 
422
- def release(conn)
423
- thread_id = if @reserved_connections[current_connection_id] == conn
424
- current_connection_id
425
- else
426
- @reserved_connections.keys.find { |k|
427
- @reserved_connections[k] == conn
428
- }
429
- end
429
+ def release(owner)
430
+ thread_id = owner.object_id
430
431
 
431
- @reserved_connections.delete thread_id if thread_id
432
+ @reserved_connections.delete thread_id
432
433
  end
433
434
 
434
435
  def new_connection
@@ -449,7 +450,7 @@ module ActiveRecord
449
450
  end
450
451
 
451
452
  def checkout_and_verify(c)
452
- c.run_callbacks :checkout do
453
+ c._run_checkout_callbacks do
453
454
  c.verify!
454
455
  end
455
456
  c
@@ -462,23 +463,44 @@ module ActiveRecord
462
463
  #
463
464
  # For example, suppose that you have 5 models, with the following hierarchy:
464
465
  #
465
- # |
466
- # +-- Book
467
- # | |
468
- # | +-- ScaryBook
469
- # | +-- GoodBook
470
- # +-- Author
471
- # +-- BankAccount
466
+ # class Author < ActiveRecord::Base
467
+ # end
472
468
  #
473
- # Suppose that Book is to connect to a separate database (i.e. one other
474
- # than the default database). Then Book, ScaryBook and GoodBook will all use
475
- # the same connection pool. Likewise, Author and BankAccount will use the
476
- # same connection pool. However, the connection pool used by Author/BankAccount
477
- # is not the same as the one used by Book/ScaryBook/GoodBook.
469
+ # class BankAccount < ActiveRecord::Base
470
+ # end
478
471
  #
479
- # Normally there is only a single ConnectionHandler instance, accessible via
480
- # ActiveRecord::Base.connection_handler. Active Record models use this to
481
- # determine the connection pool that they should use.
472
+ # class Book < ActiveRecord::Base
473
+ # establish_connection "library_db"
474
+ # end
475
+ #
476
+ # class ScaryBook < Book
477
+ # end
478
+ #
479
+ # class GoodBook < Book
480
+ # end
481
+ #
482
+ # And a database.yml that looked like this:
483
+ #
484
+ # development:
485
+ # database: my_application
486
+ # host: localhost
487
+ #
488
+ # library_db:
489
+ # database: library
490
+ # host: some.library.org
491
+ #
492
+ # Your primary database in the development environment is "my_application"
493
+ # but the Book model connects to a separate database called "library_db"
494
+ # (this can even be a database on a different machine).
495
+ #
496
+ # Book, ScaryBook and GoodBook will all use the same connection pool to
497
+ # "library_db" while Author, BankAccount, and any other models you create
498
+ # will use the default connection pool to "my_application".
499
+ #
500
+ # The various connection pools are managed by a single instance of
501
+ # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
502
+ # All Active Record models use this handler to determine the connection pool that they
503
+ # should use.
482
504
  class ConnectionHandler
483
505
  def initialize
484
506
  # These caches are keyed by klass.name, NOT klass. Keying them by klass
@@ -497,10 +519,11 @@ module ActiveRecord
497
519
  end
498
520
 
499
521
  def connection_pools
500
- ActiveSupport::Deprecation.warn(
501
- "In the next release, this will return the same as #connection_pool_list. " \
502
- "(An array of pools, rather than a hash mapping specs to pools.)"
503
- )
522
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
523
+ In the next release, this will return the same as `#connection_pool_list`.
524
+ (An array of pools, rather than a hash mapping specs to pools.)
525
+ MSG
526
+
504
527
  Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
505
528
  end
506
529
 
@@ -538,7 +561,10 @@ module ActiveRecord
538
561
  # for (not necessarily the current class).
539
562
  def retrieve_connection(klass) #:nodoc:
540
563
  pool = retrieve_connection_pool(klass)
541
- (pool && pool.connection) or raise ConnectionNotEstablished
564
+ raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
565
+ conn = pool.connection
566
+ raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
567
+ conn
542
568
  end
543
569
 
544
570
  # Returns true if a connection that's accessible to this class has
@@ -616,7 +642,7 @@ module ActiveRecord
616
642
  end
617
643
 
618
644
  def call(env)
619
- testing = env.key?('rack.test')
645
+ testing = env['rack.test']
620
646
 
621
647
  response = @app.call(env)
622
648
  response[2] = ::Rack::BodyProxy.new(response[2]) do
@@ -9,25 +9,26 @@ module ActiveRecord
9
9
  # Converts an arel AST to SQL
10
10
  def to_sql(arel, binds = [])
11
11
  if arel.respond_to?(:ast)
12
- binds = binds.dup
13
- visitor.accept(arel.ast) do
14
- quote(*binds.shift.reverse)
15
- end
12
+ collected = visitor.accept(arel.ast, collector)
13
+ collected.compile(binds.dup, self)
16
14
  else
17
15
  arel
18
16
  end
19
17
  end
20
18
 
21
- # Returns an ActiveRecord::Result instance.
22
- def select_all(arel, name = nil, binds = [])
23
- if arel.is_a?(Relation)
24
- relation = arel
25
- arel = relation.arel
26
- if !binds || binds.empty?
27
- binds = relation.bind_values
28
- end
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
29
26
  end
27
+ end
30
28
 
29
+ # Returns an ActiveRecord::Result instance.
30
+ def select_all(arel, name = nil, binds = [])
31
+ arel, binds = binds_from_relation arel, binds
31
32
  select(to_sql(arel, binds), name, binds)
32
33
  end
33
34
 
@@ -47,10 +48,7 @@ module ActiveRecord
47
48
  # Returns an array of the values of the first column in a select:
48
49
  # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
49
50
  def select_values(arel, name = nil)
50
- binds = []
51
- if arel.is_a?(Relation)
52
- arel, binds = arel.arel, arel.bind_values
53
- end
51
+ arel, binds = binds_from_relation arel, []
54
52
  select_rows(to_sql(arel, binds), name, binds).map(&:first)
55
53
  end
56
54
 
@@ -85,6 +83,11 @@ module ActiveRecord
85
83
  exec_query(sql, name, binds)
86
84
  end
87
85
 
86
+ # Executes the truncate statement.
87
+ def truncate(table_name, name = nil)
88
+ raise NotImplementedError
89
+ end
90
+
88
91
  # Executes update +sql+ statement in the context of this connection using
89
92
  # +binds+ as the bind substitutes. +name+ is logged along with
90
93
  # the executed +sql+ statement.
@@ -195,7 +198,7 @@ module ActiveRecord
195
198
  # * You are creating a nested (savepoint) transaction
196
199
  #
197
200
  # The mysql, mysql2 and postgresql adapters support setting the transaction
198
- # isolation level. However, support is disabled for mysql versions below 5,
201
+ # isolation level. However, support is disabled for MySQL versions below 5,
199
202
  # because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
200
203
  # which means the isolation level gets persisted outside the transaction.
201
204
  def transaction(options = {})
@@ -205,58 +208,30 @@ module ActiveRecord
205
208
  if options[:isolation]
206
209
  raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
207
210
  end
208
-
209
211
  yield
210
212
  else
211
- within_new_transaction(options) { yield }
213
+ transaction_manager.within_new_transaction(options) { yield }
212
214
  end
213
215
  rescue ActiveRecord::Rollback
214
216
  # rollbacks are silently swallowed
215
217
  end
216
218
 
217
- def within_new_transaction(options = {}) #:nodoc:
218
- transaction = begin_transaction(options)
219
- yield
220
- rescue Exception => error
221
- rollback_transaction if transaction
222
- raise
223
- ensure
224
- begin
225
- commit_transaction unless error
226
- rescue Exception
227
- rollback_transaction
228
- raise
229
- end
230
- end
219
+ attr_reader :transaction_manager #:nodoc:
231
220
 
232
- def current_transaction #:nodoc:
233
- @transaction
234
- end
221
+ delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction, :commit_transaction, :rollback_transaction, to: :transaction_manager
235
222
 
236
223
  def transaction_open?
237
- @transaction.open?
238
- end
239
-
240
- def begin_transaction(options = {}) #:nodoc:
241
- @transaction = @transaction.begin(options)
242
- end
243
-
244
- def commit_transaction #:nodoc:
245
- @transaction = @transaction.commit
246
- end
247
-
248
- def rollback_transaction #:nodoc:
249
- @transaction = @transaction.rollback
224
+ current_transaction.open?
250
225
  end
251
226
 
252
227
  def reset_transaction #:nodoc:
253
- @transaction = ClosedTransaction.new(self)
228
+ @transaction_manager = TransactionManager.new(self)
254
229
  end
255
230
 
256
231
  # Register a record with the current transaction so that its after_commit and after_rollback callbacks
257
232
  # can be called.
258
233
  def add_transaction_record(record)
259
- @transaction.add_record(record)
234
+ current_transaction.add_record(record)
260
235
  end
261
236
 
262
237
  # Begins the transaction (and turns off auto-committing).
@@ -312,10 +287,6 @@ module ActiveRecord
312
287
  "DEFAULT VALUES"
313
288
  end
314
289
 
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})"
317
- end
318
-
319
290
  # Sanitizes the given LIMIT parameter in order to prevent SQL injection.
320
291
  #
321
292
  # The +limit+ may be anything that can evaluate to a string via #to_s. It
@@ -328,7 +299,7 @@ module ActiveRecord
328
299
  def sanitize_limit(limit)
329
300
  if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
330
301
  limit
331
- elsif limit.to_s =~ /,/
302
+ elsif limit.to_s.include?(',')
332
303
  Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
333
304
  else
334
305
  Integer(limit)
@@ -336,8 +307,8 @@ module ActiveRecord
336
307
  end
337
308
 
338
309
  # 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.
310
+ # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in
311
+ # an UPDATE statement, so in the MySQL adapters we redefine this to do that.
341
312
  def join_to_update(update, select) #:nodoc:
342
313
  key = update.key
343
314
  subselect = subquery_for(key, select)
@@ -362,8 +333,9 @@ module ActiveRecord
362
333
 
363
334
  # Returns an ActiveRecord::Result instance.
364
335
  def select(sql, name = nil, binds = [])
336
+ exec_query(sql, name, binds)
365
337
  end
366
- undef_method :select
338
+
367
339
 
368
340
  # Returns the last auto-generated ID from the affected table.
369
341
  def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
@@ -389,6 +361,13 @@ module ActiveRecord
389
361
  row = result.rows.first
390
362
  row && row.first
391
363
  end
364
+
365
+ def binds_from_relation(relation, binds)
366
+ if relation.is_a?(Relation) && binds.empty?
367
+ relation, binds = relation.arel, relation.bind_values
368
+ end
369
+ [relation, binds]
370
+ end
392
371
  end
393
372
  end
394
373
  end
@@ -63,6 +63,7 @@ module ActiveRecord
63
63
 
64
64
  def select_all(arel, name = nil, binds = [])
65
65
  if @query_cache_enabled && !locked?(arel)
66
+ arel, binds = binds_from_relation arel, binds
66
67
  sql = to_sql(arel, binds)
67
68
  cache_sql(sql, binds) { super(sql, name, binds) }
68
69
  else
@@ -9,34 +9,11 @@ module ActiveRecord
9
9
  # records are quoted as their primary key
10
10
  return value.quoted_id if value.respond_to?(:quoted_id)
11
11
 
12
- case value
13
- when String, ActiveSupport::Multibyte::Chars
14
- value = value.to_s
15
- return "'#{quote_string(value)}'" unless column
16
-
17
- case column.type
18
- when :integer then value.to_i.to_s
19
- when :float then value.to_f.to_s
20
- else
21
- "'#{quote_string(value)}'"
22
- end
23
-
24
- when true, false
25
- if column && column.type == :integer
26
- value ? '1' : '0'
27
- else
28
- value ? quoted_true : quoted_false
29
- end
30
- # BigDecimals need to be put in a non-normalized form and quoted.
31
- when nil then "NULL"
32
- when BigDecimal then value.to_s('F')
33
- when Numeric, ActiveSupport::Duration then value.to_s
34
- when Date, Time then "'#{quoted_date(value)}'"
35
- when Symbol then "'#{quote_string(value.to_s)}'"
36
- when Class then "'#{value.to_s}'"
37
- else
38
- "'#{quote_string(YAML.dump(value))}'"
12
+ if column
13
+ value = column.cast_type.type_cast_for_database(value)
39
14
  end
15
+
16
+ _quote(value)
40
17
  end
41
18
 
42
19
  # Cast a +value+ to a type that the database understands. For example,
@@ -47,34 +24,14 @@ module ActiveRecord
47
24
  return value.id
48
25
  end
49
26
 
50
- case value
51
- when String, ActiveSupport::Multibyte::Chars
52
- value = value.to_s
53
- return value unless column
54
-
55
- case column.type
56
- when :integer then value.to_i
57
- when :float then value.to_f
58
- else
59
- value
60
- end
61
-
62
- when true, false
63
- if column && column.type == :integer
64
- value ? 1 : 0
65
- else
66
- value ? 't' : 'f'
67
- end
68
- # BigDecimals need to be put in a non-normalized form and quoted.
69
- when nil then nil
70
- when BigDecimal then value.to_s('F')
71
- when Numeric then value
72
- when Date, Time then quoted_date(value)
73
- when Symbol then value.to_s
74
- else
75
- to_type = column ? " to #{column.type}" : ""
76
- raise TypeError, "can't cast #{value.class}#{to_type}"
27
+ if column
28
+ value = column.cast_type.type_cast_for_database(value)
77
29
  end
30
+
31
+ _type_cast(value)
32
+ rescue TypeError
33
+ to_type = column ? " to #{column.type}" : ""
34
+ raise TypeError, "can't cast #{value.class}#{to_type}"
78
35
  end
79
36
 
80
37
  # Quotes a string, escaping any ' (single quote) and \ (backslash)
@@ -99,7 +56,7 @@ module ActiveRecord
99
56
  # This works for mysql and mysql2 where table.column can be used to
100
57
  # resolve ambiguity.
101
58
  #
102
- # We override this in the sqlite and postgresql adapters to use only
59
+ # We override this in the sqlite3 and postgresql adapters to use only
103
60
  # the column name (as per syntax requirements).
104
61
  def quote_table_name_for_assignment(table, attr)
105
62
  quote_table_name("#{table}.#{attr}")
@@ -109,10 +66,18 @@ module ActiveRecord
109
66
  "'t'"
110
67
  end
111
68
 
69
+ def unquoted_true
70
+ 't'
71
+ end
72
+
112
73
  def quoted_false
113
74
  "'f'"
114
75
  end
115
76
 
77
+ def unquoted_false
78
+ 'f'
79
+ end
80
+
116
81
  def quoted_date(value)
117
82
  if value.acts_like?(:time)
118
83
  zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
@@ -124,6 +89,45 @@ module ActiveRecord
124
89
 
125
90
  value.to_s(:db)
126
91
  end
92
+
93
+ private
94
+
95
+ def types_which_need_no_typecasting
96
+ [nil, Numeric, String]
97
+ end
98
+
99
+ def _quote(value)
100
+ case value
101
+ when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data
102
+ "'#{quote_string(value.to_s)}'"
103
+ when true then quoted_true
104
+ when false then quoted_false
105
+ when nil then "NULL"
106
+ # BigDecimals need to be put in a non-normalized form and quoted.
107
+ when BigDecimal then value.to_s('F')
108
+ when Numeric, ActiveSupport::Duration then value.to_s
109
+ when Date, Time then "'#{quoted_date(value)}'"
110
+ when Symbol then "'#{quote_string(value.to_s)}'"
111
+ when Class then "'#{value}'"
112
+ else
113
+ "'#{quote_string(YAML.dump(value))}'"
114
+ end
115
+ end
116
+
117
+ def _type_cast(value)
118
+ case value
119
+ when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data
120
+ value.to_s
121
+ when true then unquoted_true
122
+ when false then unquoted_false
123
+ # BigDecimals need to be put in a non-normalized form and quoted.
124
+ when BigDecimal then value.to_s('F')
125
+ when Date, Time then quoted_date(value)
126
+ when *types_which_need_no_typecasting
127
+ value
128
+ else raise TypeError
129
+ end
130
+ end
127
131
  end
128
132
  end
129
133
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/string/strip'
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  class AbstractAdapter
@@ -13,9 +15,7 @@ module ActiveRecord
13
15
  end
14
16
 
15
17
  def visit_AddColumn(o)
16
- sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
17
- sql = "ADD #{quote_column_name(o.name)} #{sql_type}"
18
- add_column_options!(sql, column_options(o))
18
+ "ADD #{accept(o)}"
19
19
  end
20
20
 
21
21
  private
@@ -23,10 +23,12 @@ module ActiveRecord
23
23
  def visit_AlterTable(o)
24
24
  sql = "ALTER TABLE #{quote_table_name(o.name)} "
25
25
  sql << o.adds.map { |col| visit_AddColumn col }.join(' ')
26
+ sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(' ')
27
+ sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(' ')
26
28
  end
27
29
 
28
30
  def visit_ColumnDefinition(o)
29
- sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
31
+ sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
30
32
  column_sql = "#{quote_column_name(o.name)} #{sql_type}"
31
33
  add_column_options!(column_sql, column_options(o)) unless o.primary_key?
32
34
  column_sql
@@ -41,6 +43,21 @@ module ActiveRecord
41
43
  create_sql
42
44
  end
43
45
 
46
+ def visit_AddForeignKey(o)
47
+ sql = <<-SQL.strip_heredoc
48
+ ADD CONSTRAINT #{quote_column_name(o.name)}
49
+ FOREIGN KEY (#{quote_column_name(o.column)})
50
+ REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)})
51
+ SQL
52
+ sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete
53
+ sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update
54
+ sql
55
+ end
56
+
57
+ def visit_DropForeignKey(name)
58
+ "DROP CONSTRAINT #{quote_column_name(name)}"
59
+ end
60
+
44
61
  def column_options(o)
45
62
  column_options = {}
46
63
  column_options[:null] = o.null unless o.null.nil?
@@ -64,7 +81,7 @@ module ActiveRecord
64
81
  end
65
82
 
66
83
  def add_column_options!(sql, options)
67
- sql << " DEFAULT #{@conn.quote(options[:default], options[:column])}" if options_include_default?(options)
84
+ sql << " DEFAULT #{quote_value(options[:default], options[:column])}" if options_include_default?(options)
68
85
  # must explicitly check for :null to allow change_column to work on migrations
69
86
  if options[:null] == false
70
87
  sql << " NOT NULL"
@@ -75,9 +92,33 @@ module ActiveRecord
75
92
  sql
76
93
  end
77
94
 
95
+ def quote_value(value, column)
96
+ column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale)
97
+ column.cast_type ||= type_for_column(column)
98
+
99
+ @conn.quote(value, column)
100
+ end
101
+
78
102
  def options_include_default?(options)
79
103
  options.include?(:default) && !(options[:null] == false && options[:default].nil?)
80
104
  end
105
+
106
+ def action_sql(action, dependency)
107
+ case dependency
108
+ when :nullify then "ON #{action} SET NULL"
109
+ when :cascade then "ON #{action} CASCADE"
110
+ when :restrict then "ON #{action} RESTRICT"
111
+ else
112
+ raise ArgumentError, <<-MSG.strip_heredoc
113
+ '#{dependency}' is not supported for :on_update or :on_delete.
114
+ Supported values are: :nullify, :cascade, :restrict
115
+ MSG
116
+ end
117
+ end
118
+
119
+ def type_for_column(column)
120
+ @conn.lookup_cast_type(column.sql_type)
121
+ end
81
122
  end
82
123
  end
83
124
  end