activerecord 6.0.0.beta1 → 6.0.1.rc1

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 (158) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +529 -10
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +7 -1
  5. data/lib/active_record/association_relation.rb +15 -6
  6. data/lib/active_record/associations.rb +4 -3
  7. data/lib/active_record/associations/association.rb +27 -2
  8. data/lib/active_record/associations/builder/association.rb +14 -18
  9. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  10. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  12. data/lib/active_record/associations/builder/has_many.rb +2 -0
  13. data/lib/active_record/associations/builder/has_one.rb +35 -1
  14. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  15. data/lib/active_record/associations/collection_association.rb +5 -6
  16. data/lib/active_record/associations/collection_proxy.rb +13 -42
  17. data/lib/active_record/associations/has_many_association.rb +1 -9
  18. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  19. data/lib/active_record/associations/join_dependency.rb +14 -9
  20. data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
  21. data/lib/active_record/associations/preloader.rb +12 -7
  22. data/lib/active_record/associations/preloader/association.rb +37 -34
  23. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  24. data/lib/active_record/attribute_methods.rb +3 -53
  25. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  26. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  27. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  28. data/lib/active_record/attribute_methods/query.rb +2 -3
  29. data/lib/active_record/attribute_methods/read.rb +3 -9
  30. data/lib/active_record/attribute_methods/write.rb +6 -12
  31. data/lib/active_record/attributes.rb +13 -0
  32. data/lib/active_record/autosave_association.rb +21 -7
  33. data/lib/active_record/base.rb +0 -1
  34. data/lib/active_record/callbacks.rb +3 -3
  35. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +134 -23
  36. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  37. data/lib/active_record/connection_adapters/abstract/database_statements.rb +105 -70
  38. data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -5
  39. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  40. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
  41. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
  42. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  43. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
  44. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  45. data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -35
  46. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +106 -138
  47. data/lib/active_record/connection_adapters/column.rb +17 -13
  48. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  49. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
  50. data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
  51. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  52. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  53. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  54. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
  55. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  56. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  57. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  58. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
  59. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  60. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  61. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  62. data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
  63. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  64. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
  66. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  67. data/lib/active_record/connection_adapters/postgresql_adapter.rb +95 -24
  68. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  69. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  70. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  71. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  72. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
  73. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +73 -118
  74. data/lib/active_record/connection_handling.rb +40 -17
  75. data/lib/active_record/core.rb +35 -24
  76. data/lib/active_record/database_configurations.rb +99 -50
  77. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  78. data/lib/active_record/database_configurations/url_config.rb +21 -16
  79. data/lib/active_record/dynamic_matchers.rb +1 -1
  80. data/lib/active_record/enum.rb +15 -0
  81. data/lib/active_record/errors.rb +18 -13
  82. data/lib/active_record/fixtures.rb +11 -6
  83. data/lib/active_record/gem_version.rb +2 -2
  84. data/lib/active_record/inheritance.rb +1 -1
  85. data/lib/active_record/insert_all.rb +179 -0
  86. data/lib/active_record/integration.rb +13 -1
  87. data/lib/active_record/internal_metadata.rb +5 -1
  88. data/lib/active_record/locking/optimistic.rb +3 -4
  89. data/lib/active_record/log_subscriber.rb +1 -1
  90. data/lib/active_record/middleware/database_selector.rb +75 -0
  91. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  92. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  93. data/lib/active_record/migration.rb +62 -44
  94. data/lib/active_record/migration/command_recorder.rb +28 -14
  95. data/lib/active_record/migration/compatibility.rb +72 -63
  96. data/lib/active_record/model_schema.rb +3 -0
  97. data/lib/active_record/persistence.rb +212 -19
  98. data/lib/active_record/querying.rb +18 -14
  99. data/lib/active_record/railtie.rb +9 -1
  100. data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
  101. data/lib/active_record/railties/databases.rake +124 -25
  102. data/lib/active_record/reflection.rb +18 -32
  103. data/lib/active_record/relation.rb +185 -35
  104. data/lib/active_record/relation/calculations.rb +40 -44
  105. data/lib/active_record/relation/delegation.rb +23 -31
  106. data/lib/active_record/relation/finder_methods.rb +23 -14
  107. data/lib/active_record/relation/merger.rb +11 -16
  108. data/lib/active_record/relation/query_attribute.rb +5 -3
  109. data/lib/active_record/relation/query_methods.rb +230 -69
  110. data/lib/active_record/relation/spawn_methods.rb +1 -1
  111. data/lib/active_record/relation/where_clause.rb +10 -10
  112. data/lib/active_record/sanitization.rb +33 -4
  113. data/lib/active_record/schema.rb +1 -1
  114. data/lib/active_record/schema_dumper.rb +10 -1
  115. data/lib/active_record/schema_migration.rb +1 -1
  116. data/lib/active_record/scoping.rb +6 -7
  117. data/lib/active_record/scoping/default.rb +7 -15
  118. data/lib/active_record/scoping/named.rb +10 -2
  119. data/lib/active_record/statement_cache.rb +2 -2
  120. data/lib/active_record/store.rb +48 -0
  121. data/lib/active_record/table_metadata.rb +9 -13
  122. data/lib/active_record/tasks/database_tasks.rb +109 -6
  123. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  124. data/lib/active_record/test_databases.rb +1 -16
  125. data/lib/active_record/test_fixtures.rb +2 -2
  126. data/lib/active_record/timestamp.rb +35 -19
  127. data/lib/active_record/touch_later.rb +4 -2
  128. data/lib/active_record/transactions.rb +56 -46
  129. data/lib/active_record/type_caster/connection.rb +16 -10
  130. data/lib/active_record/validations.rb +1 -0
  131. data/lib/active_record/validations/uniqueness.rb +4 -4
  132. data/lib/arel.rb +18 -4
  133. data/lib/arel/insert_manager.rb +3 -3
  134. data/lib/arel/nodes.rb +2 -1
  135. data/lib/arel/nodes/and.rb +1 -1
  136. data/lib/arel/nodes/case.rb +1 -1
  137. data/lib/arel/nodes/comment.rb +29 -0
  138. data/lib/arel/nodes/select_core.rb +16 -12
  139. data/lib/arel/nodes/unary.rb +1 -0
  140. data/lib/arel/nodes/values_list.rb +2 -17
  141. data/lib/arel/select_manager.rb +10 -10
  142. data/lib/arel/visitors/depth_first.rb +7 -2
  143. data/lib/arel/visitors/dot.rb +7 -2
  144. data/lib/arel/visitors/ibm_db.rb +13 -0
  145. data/lib/arel/visitors/informix.rb +6 -0
  146. data/lib/arel/visitors/mssql.rb +15 -1
  147. data/lib/arel/visitors/oracle12.rb +4 -5
  148. data/lib/arel/visitors/postgresql.rb +4 -10
  149. data/lib/arel/visitors/to_sql.rb +107 -131
  150. data/lib/arel/visitors/visitor.rb +9 -5
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  152. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  154. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  155. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  156. metadata +19 -12
  157. data/lib/active_record/collection_cache_key.rb +0 -53
  158. data/lib/arel/nodes/values.rb +0 -16
@@ -288,7 +288,6 @@ module ActiveRecord #:nodoc:
288
288
  extend Explain
289
289
  extend Enum
290
290
  extend Delegation::DelegateCache
291
- extend CollectionCacheKey
292
291
  extend Aggregations::ClassMethods
293
292
 
294
293
  include Core
@@ -95,7 +95,7 @@ module ActiveRecord
95
95
  #
96
96
  # private
97
97
  # def delete_parents
98
- # self.class.where(parent_id: id).delete_all
98
+ # self.class.delete_by(parent_id: id)
99
99
  # end
100
100
  # end
101
101
  #
@@ -324,7 +324,7 @@ module ActiveRecord
324
324
 
325
325
  private
326
326
 
327
- def create_or_update(*)
327
+ def create_or_update(**)
328
328
  _run_save_callbacks { super }
329
329
  end
330
330
 
@@ -332,7 +332,7 @@ module ActiveRecord
332
332
  _run_create_callbacks { super }
333
333
  end
334
334
 
335
- def _update_record(*)
335
+ def _update_record
336
336
  _run_update_callbacks { super }
337
337
  end
338
338
  end
@@ -3,6 +3,7 @@
3
3
  require "thread"
4
4
  require "concurrent/map"
5
5
  require "monitor"
6
+ require "weakref"
6
7
 
7
8
  module ActiveRecord
8
9
  # Raised when a connection could not be obtained within the connection
@@ -19,6 +20,26 @@ module ActiveRecord
19
20
  end
20
21
 
21
22
  module ConnectionAdapters
23
+ module AbstractPool # :nodoc:
24
+ def get_schema_cache(connection)
25
+ @schema_cache ||= SchemaCache.new(connection)
26
+ @schema_cache.connection = connection
27
+ @schema_cache
28
+ end
29
+
30
+ def set_schema_cache(cache)
31
+ @schema_cache = cache
32
+ end
33
+ end
34
+
35
+ class NullPool # :nodoc:
36
+ include ConnectionAdapters::AbstractPool
37
+
38
+ def initialize
39
+ @schema_cache = nil
40
+ end
41
+ end
42
+
22
43
  # Connection pool base class for managing Active Record database
23
44
  # connections.
24
45
  #
@@ -185,7 +206,7 @@ module ActiveRecord
185
206
  def wait_poll(timeout)
186
207
  @num_waiting += 1
187
208
 
188
- t0 = Time.now
209
+ t0 = Concurrent.monotonic_time
189
210
  elapsed = 0
190
211
  loop do
191
212
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@@ -194,7 +215,7 @@ module ActiveRecord
194
215
 
195
216
  return remove if any?
196
217
 
197
- elapsed = Time.now - t0
218
+ elapsed = Concurrent.monotonic_time - t0
198
219
  if elapsed >= timeout
199
220
  msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
200
221
  [timeout, elapsed]
@@ -294,23 +315,59 @@ module ActiveRecord
294
315
  @frequency = frequency
295
316
  end
296
317
 
318
+ @mutex = Mutex.new
319
+ @pools = {}
320
+ @threads = {}
321
+
322
+ class << self
323
+ def register_pool(pool, frequency) # :nodoc:
324
+ @mutex.synchronize do
325
+ unless @threads[frequency]&.alive?
326
+ @threads[frequency] = spawn_thread(frequency)
327
+ end
328
+ @pools[frequency] ||= []
329
+ @pools[frequency] << WeakRef.new(pool)
330
+ end
331
+ end
332
+
333
+ private
334
+
335
+ def spawn_thread(frequency)
336
+ Thread.new(frequency) do |t|
337
+ running = true
338
+ while running
339
+ sleep t
340
+ @mutex.synchronize do
341
+ @pools[frequency].select!(&:weakref_alive?)
342
+ @pools[frequency].each do |p|
343
+ p.reap
344
+ p.flush
345
+ rescue WeakRef::RefError
346
+ end
347
+
348
+ if @pools[frequency].empty?
349
+ @pools.delete(frequency)
350
+ @threads.delete(frequency)
351
+ running = false
352
+ end
353
+ end
354
+ end
355
+ end
356
+ end
357
+ end
358
+
297
359
  def run
298
360
  return unless frequency && frequency > 0
299
- Thread.new(frequency, pool) { |t, p|
300
- loop do
301
- sleep t
302
- p.reap
303
- p.flush
304
- end
305
- }
361
+ self.class.register_pool(pool, frequency)
306
362
  end
307
363
  end
308
364
 
309
365
  include MonitorMixin
310
366
  include QueryCache::ConnectionPoolConfiguration
367
+ include ConnectionAdapters::AbstractPool
311
368
 
312
369
  attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
313
- attr_reader :spec, :connections, :size, :reaper
370
+ attr_reader :spec, :size, :reaper
314
371
 
315
372
  # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
316
373
  # object which describes database connection information (e.g. adapter,
@@ -379,7 +436,7 @@ module ActiveRecord
379
436
  # #connection can be called any number of times; the connection is
380
437
  # held in a cache keyed by a thread.
381
438
  def connection
382
- @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout
439
+ @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
383
440
  end
384
441
 
385
442
  # Returns true if there is an open connection being used for the current thread.
@@ -388,7 +445,7 @@ module ActiveRecord
388
445
  # #connection or #with_connection methods. Connections obtained through
389
446
  # #checkout will not be detected by #active_connection?
390
447
  def active_connection?
391
- @thread_cached_conns[connection_cache_key(Thread.current)]
448
+ @thread_cached_conns[connection_cache_key(current_thread)]
392
449
  end
393
450
 
394
451
  # Signal that the thread is finished with the current connection.
@@ -423,6 +480,21 @@ module ActiveRecord
423
480
  synchronize { @connections.any? }
424
481
  end
425
482
 
483
+ # Returns an array containing the connections currently in the pool.
484
+ # Access to the array does not require synchronization on the pool because
485
+ # the array is newly created and not retained by the pool.
486
+ #
487
+ # However; this method bypasses the ConnectionPool's thread-safe connection
488
+ # access pattern. A returned connection may be owned by another thread,
489
+ # unowned, or by happen-stance owned by the calling thread.
490
+ #
491
+ # Calling methods on a connection without ownership is subject to the
492
+ # thread-safety guarantees of the underlying method. Many of the methods
493
+ # on connection adapter classes are inherently multi-thread unsafe.
494
+ def connections
495
+ synchronize { @connections.dup }
496
+ end
497
+
426
498
  # Disconnects all connections in the pool, and clears the pool.
427
499
  #
428
500
  # Raises:
@@ -578,6 +650,7 @@ module ActiveRecord
578
650
  # or a thread dies unexpectedly.
579
651
  def reap
580
652
  stale_connections = synchronize do
653
+ return unless @connections
581
654
  @connections.select do |conn|
582
655
  conn.in_use? && !conn.owner.alive?
583
656
  end.each do |conn|
@@ -602,6 +675,7 @@ module ActiveRecord
602
675
  return if minimum_idle.nil?
603
676
 
604
677
  idle_connections = synchronize do
678
+ return unless @connections
605
679
  @connections.select do |conn|
606
680
  !conn.in_use? && conn.seconds_idle >= minimum_idle
607
681
  end.each do |conn|
@@ -668,6 +742,10 @@ module ActiveRecord
668
742
  thread
669
743
  end
670
744
 
745
+ def current_thread
746
+ @lock_thread || Thread.current
747
+ end
748
+
671
749
  # Take control of all existing connections so a "group" action such as
672
750
  # reload/disconnect can be performed safely. It is no longer enough to
673
751
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -686,13 +764,13 @@ module ActiveRecord
686
764
  end
687
765
 
688
766
  newly_checked_out = []
689
- timeout_time = Time.now + (@checkout_timeout * 2)
767
+ timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2)
690
768
 
691
769
  @available.with_a_bias_for(Thread.current) do
692
770
  loop do
693
771
  synchronize do
694
772
  return if collected_conns.size == @connections.size && @now_connecting == 0
695
- remaining_timeout = timeout_time - Time.now
773
+ remaining_timeout = timeout_time - Concurrent.monotonic_time
696
774
  remaining_timeout = 0 if remaining_timeout < 0
697
775
  conn = checkout_for_exclusive_access(remaining_timeout)
698
776
  collected_conns << conn
@@ -809,7 +887,7 @@ module ActiveRecord
809
887
 
810
888
  def new_connection
811
889
  Base.send(spec.adapter_method, spec.config).tap do |conn|
812
- conn.schema_cache = schema_cache.dup if schema_cache
890
+ conn.check_version
813
891
  end
814
892
  end
815
893
 
@@ -915,6 +993,16 @@ module ActiveRecord
915
993
  # about the model. The model needs to pass a specification name to the handler,
916
994
  # in order to look up the correct connection pool.
917
995
  class ConnectionHandler
996
+ def self.create_owner_to_pool # :nodoc:
997
+ Concurrent::Map.new(initial_capacity: 2) do |h, k|
998
+ # Discard the parent's connection pools immediately; we have no need
999
+ # of them
1000
+ discard_unowned_pools(h)
1001
+
1002
+ h[k] = Concurrent::Map.new(initial_capacity: 2)
1003
+ end
1004
+ end
1005
+
918
1006
  def self.unowned_pool_finalizer(pid_map) # :nodoc:
919
1007
  lambda do |_|
920
1008
  discard_unowned_pools(pid_map)
@@ -929,19 +1017,33 @@ module ActiveRecord
929
1017
 
930
1018
  def initialize
931
1019
  # These caches are keyed by spec.name (ConnectionSpecification#name).
932
- @owner_to_pool = Concurrent::Map.new(initial_capacity: 2) do |h, k|
933
- # Discard the parent's connection pools immediately; we have no need
934
- # of them
935
- ConnectionHandler.discard_unowned_pools(h)
936
-
937
- h[k] = Concurrent::Map.new(initial_capacity: 2)
938
- end
1020
+ @owner_to_pool = ConnectionHandler.create_owner_to_pool
939
1021
 
940
1022
  # Backup finalizer: if the forked child never needed a pool, the above
941
1023
  # early discard has not occurred
942
1024
  ObjectSpace.define_finalizer self, ConnectionHandler.unowned_pool_finalizer(@owner_to_pool)
943
1025
  end
944
1026
 
1027
+ def prevent_writes # :nodoc:
1028
+ Thread.current[:prevent_writes]
1029
+ end
1030
+
1031
+ def prevent_writes=(prevent_writes) # :nodoc:
1032
+ Thread.current[:prevent_writes] = prevent_writes
1033
+ end
1034
+
1035
+ # Prevent writing to the database regardless of role.
1036
+ #
1037
+ # In some cases you may want to prevent writes to the database
1038
+ # even if you are on a database that can write. `while_preventing_writes`
1039
+ # will prevent writes to the database for the duration of the block.
1040
+ def while_preventing_writes(enabled = true)
1041
+ original, self.prevent_writes = self.prevent_writes, enabled
1042
+ yield
1043
+ ensure
1044
+ self.prevent_writes = original
1045
+ end
1046
+
945
1047
  def connection_pool_list
946
1048
  owner_to_pool.values.compact
947
1049
  end
@@ -1006,7 +1108,16 @@ module ActiveRecord
1006
1108
  # for (not necessarily the current class).
1007
1109
  def retrieve_connection(spec_name) #:nodoc:
1008
1110
  pool = retrieve_connection_pool(spec_name)
1009
- raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." unless pool
1111
+
1112
+ unless pool
1113
+ # multiple database application
1114
+ if ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
1115
+ raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
1116
+ else
1117
+ raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found."
1118
+ end
1119
+ end
1120
+
1010
1121
  pool.connection
1011
1122
  end
1012
1123
 
@@ -5,20 +5,24 @@ require "active_support/deprecation"
5
5
  module ActiveRecord
6
6
  module ConnectionAdapters # :nodoc:
7
7
  module DatabaseLimits
8
+ def max_identifier_length # :nodoc:
9
+ 64
10
+ end
11
+
8
12
  # Returns the maximum length of a table alias.
9
13
  def table_alias_length
10
- 255
14
+ max_identifier_length
11
15
  end
12
16
 
13
17
  # Returns the maximum length of a column name.
14
18
  def column_name_length
15
- 64
19
+ max_identifier_length
16
20
  end
17
21
  deprecate :column_name_length
18
22
 
19
23
  # Returns the maximum length of a table name.
20
24
  def table_name_length
21
- 64
25
+ max_identifier_length
22
26
  end
23
27
  deprecate :table_name_length
24
28
 
@@ -33,7 +37,7 @@ module ActiveRecord
33
37
 
34
38
  # Returns the maximum length of an index name.
35
39
  def index_name_length
36
- 64
40
+ max_identifier_length
37
41
  end
38
42
 
39
43
  # Returns the maximum number of columns per table.
@@ -20,9 +20,22 @@ module ActiveRecord
20
20
  raise "Passing bind parameters with an arel AST is forbidden. " \
21
21
  "The values must be stored on the AST directly"
22
22
  end
23
- sql, binds = visitor.compile(arel_or_sql_string.ast, collector)
24
- [sql.freeze, binds || []]
23
+
24
+ if prepared_statements
25
+ sql, binds = visitor.compile(arel_or_sql_string.ast, collector)
26
+
27
+ if binds.length > bind_params_length
28
+ unprepared_statement do
29
+ sql, binds = to_sql_and_binds(arel_or_sql_string)
30
+ visitor.preparable = false
31
+ end
32
+ end
33
+ else
34
+ sql = visitor.compile(arel_or_sql_string.ast, collector)
35
+ end
36
+ [sql.freeze, binds]
25
37
  else
38
+ visitor.preparable = false if prepared_statements
26
39
  [arel_or_sql_string.dup.freeze, binds]
27
40
  end
28
41
  end
@@ -47,13 +60,8 @@ module ActiveRecord
47
60
  arel = arel_from_relation(arel)
48
61
  sql, binds = to_sql_and_binds(arel, binds)
49
62
 
50
- if !prepared_statements || (arel.is_a?(String) && preparable.nil?)
51
- preparable = false
52
- elsif binds.length > bind_params_length
53
- sql, binds = unprepared_statement { to_sql_and_binds(arel) }
54
- preparable = false
55
- else
56
- preparable = visitor.preparable
63
+ if preparable.nil?
64
+ preparable = prepared_statements ? visitor.preparable : false
57
65
  end
58
66
 
59
67
  if prepared_statements && preparable
@@ -123,7 +131,7 @@ module ActiveRecord
123
131
  # +binds+ as the bind substitutes. +name+ is logged along with
124
132
  # the executed +sql+ statement.
125
133
  def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
126
- sql, binds = sql_for_insert(sql, pk, sequence_name, binds)
134
+ sql, binds = sql_for_insert(sql, pk, binds)
127
135
  exec_query(sql, name, binds)
128
136
  end
129
137
 
@@ -134,11 +142,6 @@ module ActiveRecord
134
142
  exec_query(sql, name, binds)
135
143
  end
136
144
 
137
- # Executes the truncate statement.
138
- def truncate(table_name, name = nil)
139
- raise NotImplementedError
140
- end
141
-
142
145
  # Executes update +sql+ statement in the context of this connection using
143
146
  # +binds+ as the bind substitutes. +name+ is logged along with
144
147
  # the executed +sql+ statement.
@@ -146,6 +149,10 @@ module ActiveRecord
146
149
  exec_query(sql, name, binds)
147
150
  end
148
151
 
152
+ def exec_insert_all(sql, name) # :nodoc:
153
+ exec_query(sql, name)
154
+ end
155
+
149
156
  # Executes an INSERT query and returns the new record's ID
150
157
  #
151
158
  # +id_value+ will be returned unless the value is +nil+, in
@@ -173,6 +180,23 @@ module ActiveRecord
173
180
  exec_delete(sql, name, binds)
174
181
  end
175
182
 
183
+ # Executes the truncate statement.
184
+ def truncate(table_name, name = nil)
185
+ execute(build_truncate_statements(table_name), name)
186
+ end
187
+
188
+ def truncate_tables(*table_names) # :nodoc:
189
+ return if table_names.empty?
190
+
191
+ with_multi_statements do
192
+ disable_referential_integrity do
193
+ Array(build_truncate_statements(*table_names)).each do |sql|
194
+ execute_batch(sql, "Truncate Tables")
195
+ end
196
+ end
197
+ end
198
+ end
199
+
176
200
  # Runs the given block in a database transaction, and returns the result
177
201
  # of the block.
178
202
  #
@@ -333,46 +357,20 @@ module ActiveRecord
333
357
  # We keep this method to provide fallback
334
358
  # for databases like sqlite that do not support bulk inserts.
335
359
  def insert_fixture(fixture, table_name)
336
- fixture = fixture.stringify_keys
337
-
338
- columns = schema_cache.columns_hash(table_name)
339
- binds = fixture.map do |name, value|
340
- if column = columns[name]
341
- type = lookup_cast_type_from_column(column)
342
- Relation::QueryAttribute.new(name, value, type)
343
- else
344
- raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.)
345
- end
346
- end
347
-
348
- table = Arel::Table.new(table_name)
349
-
350
- values = binds.map do |bind|
351
- value = with_yaml_fallback(bind.value_for_database)
352
- [table[bind.name], value]
353
- end
354
-
355
- manager = Arel::InsertManager.new
356
- manager.into(table)
357
- manager.insert(values)
358
- execute manager.to_sql, "Fixture Insert"
360
+ execute(build_fixture_sql(Array.wrap(fixture), table_name), "Fixture Insert")
359
361
  end
360
362
 
361
363
  def insert_fixtures_set(fixture_set, tables_to_delete = [])
362
- fixture_inserts = fixture_set.map do |table_name, fixtures|
363
- next if fixtures.empty?
364
-
365
- build_fixture_sql(fixtures, table_name)
366
- end.compact
367
-
368
- table_deletes = tables_to_delete.map { |table| +"DELETE FROM #{quote_table_name table}" }
369
- total_sql = Array.wrap(combine_multi_statements(table_deletes + fixture_inserts))
370
-
371
- disable_referential_integrity do
372
- transaction(requires_new: true) do
373
- total_sql.each do |sql|
374
- execute sql, "Fixtures Load"
375
- yield if block_given?
364
+ fixture_inserts = build_fixture_statements(fixture_set)
365
+ table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
366
+ total_sql = Array(combine_multi_statements(table_deletes + fixture_inserts))
367
+
368
+ with_multi_statements do
369
+ disable_referential_integrity do
370
+ transaction(requires_new: true) do
371
+ total_sql.each do |sql|
372
+ execute_batch(sql, "Fixtures Load")
373
+ end
376
374
  end
377
375
  end
378
376
  end
@@ -396,15 +394,33 @@ module ActiveRecord
396
394
  end
397
395
  end
398
396
 
397
+ # Fixture value is quoted by Arel, however scalar values
398
+ # are not quotable. In this case we want to convert
399
+ # the column value to YAML.
400
+ def with_yaml_fallback(value) # :nodoc:
401
+ if value.is_a?(Hash) || value.is_a?(Array)
402
+ YAML.dump(value)
403
+ else
404
+ value
405
+ end
406
+ end
407
+
399
408
  private
409
+ def execute_batch(sql, name = nil)
410
+ execute(sql, name)
411
+ end
412
+
413
+ DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
414
+ private_constant :DEFAULT_INSERT_VALUE
415
+
400
416
  def default_insert_value(column)
401
- Arel.sql("DEFAULT")
417
+ DEFAULT_INSERT_VALUE
402
418
  end
403
419
 
404
420
  def build_fixture_sql(fixtures, table_name)
405
421
  columns = schema_cache.columns_hash(table_name)
406
422
 
407
- values = fixtures.map do |fixture|
423
+ values_list = fixtures.map do |fixture|
408
424
  fixture = fixture.stringify_keys
409
425
 
410
426
  unknown_columns = fixture.keys - columns.keys
@@ -415,8 +431,7 @@ module ActiveRecord
415
431
  columns.map do |name, column|
416
432
  if fixture.key?(name)
417
433
  type = lookup_cast_type_from_column(column)
418
- bind = Relation::QueryAttribute.new(name, fixture[name], type)
419
- with_yaml_fallback(bind.value_for_database)
434
+ with_yaml_fallback(type.serialize(fixture[name]))
420
435
  else
421
436
  default_insert_value(column)
422
437
  end
@@ -426,12 +441,43 @@ module ActiveRecord
426
441
  table = Arel::Table.new(table_name)
427
442
  manager = Arel::InsertManager.new
428
443
  manager.into(table)
429
- columns.each_key { |column| manager.columns << table[column] }
430
- manager.values = manager.create_values_list(values)
431
444
 
445
+ if values_list.size == 1
446
+ values = values_list.shift
447
+ new_values = []
448
+ columns.each_key.with_index { |column, i|
449
+ unless values[i].equal?(DEFAULT_INSERT_VALUE)
450
+ new_values << values[i]
451
+ manager.columns << table[column]
452
+ end
453
+ }
454
+ values_list << new_values
455
+ else
456
+ columns.each_key { |column| manager.columns << table[column] }
457
+ end
458
+
459
+ manager.values = manager.create_values_list(values_list)
432
460
  manager.to_sql
433
461
  end
434
462
 
463
+ def build_fixture_statements(fixture_set)
464
+ fixture_set.map do |table_name, fixtures|
465
+ next if fixtures.empty?
466
+ build_fixture_sql(fixtures, table_name)
467
+ end.compact
468
+ end
469
+
470
+ def build_truncate_statements(*table_names)
471
+ truncate_tables = table_names.map do |table_name|
472
+ "TRUNCATE TABLE #{quote_table_name(table_name)}"
473
+ end
474
+ combine_multi_statements(truncate_tables)
475
+ end
476
+
477
+ def with_multi_statements
478
+ yield
479
+ end
480
+
435
481
  def combine_multi_statements(total_sql)
436
482
  total_sql.join(";\n")
437
483
  end
@@ -445,7 +491,7 @@ module ActiveRecord
445
491
  exec_query(sql, name, binds, prepare: true)
446
492
  end
447
493
 
448
- def sql_for_insert(sql, pk, sequence_name, binds)
494
+ def sql_for_insert(sql, pk, binds)
449
495
  [sql, binds]
450
496
  end
451
497
 
@@ -465,17 +511,6 @@ module ActiveRecord
465
511
  relation
466
512
  end
467
513
  end
468
-
469
- # Fixture value is quoted by Arel, however scalar values
470
- # are not quotable. In this case we want to convert
471
- # the column value to YAML.
472
- def with_yaml_fallback(value)
473
- if value.is_a?(Hash) || value.is_a?(Array)
474
- YAML.dump(value)
475
- else
476
- value
477
- end
478
- end
479
514
  end
480
515
  end
481
516
  end