activerecord 6.0.0.beta1 → 6.0.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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +455 -9
  3. data/README.rdoc +3 -1
  4. data/lib/active_record/associations/association.rb +18 -1
  5. data/lib/active_record/associations/builder/association.rb +14 -18
  6. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  7. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  8. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  9. data/lib/active_record/associations/builder/has_many.rb +2 -0
  10. data/lib/active_record/associations/builder/has_one.rb +35 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  12. data/lib/active_record/associations/collection_association.rb +5 -6
  13. data/lib/active_record/associations/collection_proxy.rb +13 -42
  14. data/lib/active_record/associations/has_many_association.rb +1 -9
  15. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  16. data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
  17. data/lib/active_record/associations/join_dependency.rb +10 -9
  18. data/lib/active_record/associations/preloader/association.rb +37 -34
  19. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  20. data/lib/active_record/associations/preloader.rb +11 -6
  21. data/lib/active_record/associations.rb +3 -2
  22. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  23. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  24. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  25. data/lib/active_record/attribute_methods/query.rb +2 -3
  26. data/lib/active_record/attribute_methods/read.rb +3 -9
  27. data/lib/active_record/attribute_methods/write.rb +6 -12
  28. data/lib/active_record/attribute_methods.rb +3 -53
  29. data/lib/active_record/attributes.rb +13 -0
  30. data/lib/active_record/autosave_association.rb +15 -5
  31. data/lib/active_record/base.rb +0 -1
  32. data/lib/active_record/callbacks.rb +3 -3
  33. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +124 -23
  34. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  35. data/lib/active_record/connection_adapters/abstract/database_statements.rb +101 -70
  36. data/lib/active_record/connection_adapters/abstract/query_cache.rb +11 -5
  37. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  38. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
  39. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
  40. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
  42. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  43. data/lib/active_record/connection_adapters/abstract_adapter.rb +108 -39
  44. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +93 -134
  45. data/lib/active_record/connection_adapters/column.rb +17 -13
  46. data/lib/active_record/connection_adapters/connection_specification.rb +1 -1
  47. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
  48. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
  49. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  50. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  51. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  52. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
  53. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  54. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  55. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  56. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  58. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  59. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  60. data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
  61. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  62. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
  63. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
  64. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  65. data/lib/active_record/connection_adapters/postgresql_adapter.rb +91 -24
  66. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  67. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  68. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  69. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +69 -118
  72. data/lib/active_record/connection_handling.rb +32 -16
  73. data/lib/active_record/core.rb +27 -20
  74. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  75. data/lib/active_record/database_configurations/url_config.rb +21 -16
  76. data/lib/active_record/database_configurations.rb +99 -50
  77. data/lib/active_record/dynamic_matchers.rb +1 -1
  78. data/lib/active_record/enum.rb +15 -0
  79. data/lib/active_record/errors.rb +18 -13
  80. data/lib/active_record/fixtures.rb +11 -6
  81. data/lib/active_record/gem_version.rb +1 -1
  82. data/lib/active_record/inheritance.rb +1 -1
  83. data/lib/active_record/insert_all.rb +179 -0
  84. data/lib/active_record/integration.rb +13 -1
  85. data/lib/active_record/internal_metadata.rb +5 -1
  86. data/lib/active_record/locking/optimistic.rb +3 -4
  87. data/lib/active_record/log_subscriber.rb +1 -1
  88. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  89. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  90. data/lib/active_record/middleware/database_selector.rb +75 -0
  91. data/lib/active_record/migration/command_recorder.rb +28 -14
  92. data/lib/active_record/migration/compatibility.rb +72 -63
  93. data/lib/active_record/migration.rb +62 -44
  94. data/lib/active_record/persistence.rb +212 -19
  95. data/lib/active_record/querying.rb +18 -14
  96. data/lib/active_record/railtie.rb +9 -1
  97. data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
  98. data/lib/active_record/railties/databases.rake +124 -25
  99. data/lib/active_record/reflection.rb +18 -32
  100. data/lib/active_record/relation/calculations.rb +40 -44
  101. data/lib/active_record/relation/delegation.rb +23 -31
  102. data/lib/active_record/relation/finder_methods.rb +13 -13
  103. data/lib/active_record/relation/merger.rb +11 -16
  104. data/lib/active_record/relation/query_attribute.rb +5 -3
  105. data/lib/active_record/relation/query_methods.rb +217 -68
  106. data/lib/active_record/relation/spawn_methods.rb +1 -1
  107. data/lib/active_record/relation/where_clause.rb +10 -10
  108. data/lib/active_record/relation.rb +184 -35
  109. data/lib/active_record/sanitization.rb +33 -4
  110. data/lib/active_record/schema.rb +1 -1
  111. data/lib/active_record/schema_dumper.rb +10 -1
  112. data/lib/active_record/schema_migration.rb +1 -1
  113. data/lib/active_record/scoping/default.rb +7 -15
  114. data/lib/active_record/scoping/named.rb +10 -2
  115. data/lib/active_record/scoping.rb +6 -7
  116. data/lib/active_record/statement_cache.rb +2 -2
  117. data/lib/active_record/store.rb +48 -0
  118. data/lib/active_record/table_metadata.rb +9 -13
  119. data/lib/active_record/tasks/database_tasks.rb +109 -6
  120. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  121. data/lib/active_record/test_databases.rb +1 -16
  122. data/lib/active_record/test_fixtures.rb +2 -2
  123. data/lib/active_record/timestamp.rb +35 -19
  124. data/lib/active_record/touch_later.rb +4 -2
  125. data/lib/active_record/transactions.rb +55 -45
  126. data/lib/active_record/type_caster/connection.rb +16 -10
  127. data/lib/active_record/validations/uniqueness.rb +4 -4
  128. data/lib/active_record/validations.rb +1 -0
  129. data/lib/active_record.rb +7 -1
  130. data/lib/arel/insert_manager.rb +3 -3
  131. data/lib/arel/nodes/and.rb +1 -1
  132. data/lib/arel/nodes/case.rb +1 -1
  133. data/lib/arel/nodes/comment.rb +29 -0
  134. data/lib/arel/nodes/select_core.rb +16 -12
  135. data/lib/arel/nodes/unary.rb +1 -0
  136. data/lib/arel/nodes/values_list.rb +2 -17
  137. data/lib/arel/nodes.rb +2 -1
  138. data/lib/arel/select_manager.rb +10 -10
  139. data/lib/arel/visitors/depth_first.rb +7 -2
  140. data/lib/arel/visitors/dot.rb +7 -2
  141. data/lib/arel/visitors/ibm_db.rb +13 -0
  142. data/lib/arel/visitors/informix.rb +6 -0
  143. data/lib/arel/visitors/mssql.rb +15 -1
  144. data/lib/arel/visitors/oracle12.rb +4 -5
  145. data/lib/arel/visitors/postgresql.rb +4 -10
  146. data/lib/arel/visitors/to_sql.rb +107 -131
  147. data/lib/arel/visitors/visitor.rb +9 -5
  148. data/lib/arel.rb +7 -0
  149. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  150. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  151. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  152. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  153. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  154. metadata +17 -13
  155. data/lib/active_record/collection_cache_key.rb +0 -53
  156. data/lib/arel/nodes/values.rb +0 -16
@@ -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,51 @@ module ActiveRecord
294
315
  @frequency = frequency
295
316
  end
296
317
 
318
+ @mutex = Mutex.new
319
+ @pools = {}
320
+
321
+ class << self
322
+ def register_pool(pool, frequency) # :nodoc:
323
+ @mutex.synchronize do
324
+ unless @pools.key?(frequency)
325
+ @pools[frequency] = []
326
+ spawn_thread(frequency)
327
+ end
328
+ @pools[frequency] << WeakRef.new(pool)
329
+ end
330
+ end
331
+
332
+ private
333
+
334
+ def spawn_thread(frequency)
335
+ Thread.new(frequency) do |t|
336
+ loop do
337
+ sleep t
338
+ @mutex.synchronize do
339
+ @pools[frequency].select!(&:weakref_alive?)
340
+ @pools[frequency].each do |p|
341
+ p.reap
342
+ p.flush
343
+ rescue WeakRef::RefError
344
+ end
345
+ end
346
+ end
347
+ end
348
+ end
349
+ end
350
+
297
351
  def run
298
352
  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
- }
353
+ self.class.register_pool(pool, frequency)
306
354
  end
307
355
  end
308
356
 
309
357
  include MonitorMixin
310
358
  include QueryCache::ConnectionPoolConfiguration
359
+ include ConnectionAdapters::AbstractPool
311
360
 
312
361
  attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
313
- attr_reader :spec, :connections, :size, :reaper
362
+ attr_reader :spec, :size, :reaper
314
363
 
315
364
  # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
316
365
  # object which describes database connection information (e.g. adapter,
@@ -379,7 +428,7 @@ module ActiveRecord
379
428
  # #connection can be called any number of times; the connection is
380
429
  # held in a cache keyed by a thread.
381
430
  def connection
382
- @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout
431
+ @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
383
432
  end
384
433
 
385
434
  # Returns true if there is an open connection being used for the current thread.
@@ -388,7 +437,7 @@ module ActiveRecord
388
437
  # #connection or #with_connection methods. Connections obtained through
389
438
  # #checkout will not be detected by #active_connection?
390
439
  def active_connection?
391
- @thread_cached_conns[connection_cache_key(Thread.current)]
440
+ @thread_cached_conns[connection_cache_key(current_thread)]
392
441
  end
393
442
 
394
443
  # Signal that the thread is finished with the current connection.
@@ -423,6 +472,21 @@ module ActiveRecord
423
472
  synchronize { @connections.any? }
424
473
  end
425
474
 
475
+ # Returns an array containing the connections currently in the pool.
476
+ # Access to the array does not require synchronization on the pool because
477
+ # the array is newly created and not retained by the pool.
478
+ #
479
+ # However; this method bypasses the ConnectionPool's thread-safe connection
480
+ # access pattern. A returned connection may be owned by another thread,
481
+ # unowned, or by happen-stance owned by the calling thread.
482
+ #
483
+ # Calling methods on a connection without ownership is subject to the
484
+ # thread-safety guarantees of the underlying method. Many of the methods
485
+ # on connection adapter classes are inherently multi-thread unsafe.
486
+ def connections
487
+ synchronize { @connections.dup }
488
+ end
489
+
426
490
  # Disconnects all connections in the pool, and clears the pool.
427
491
  #
428
492
  # Raises:
@@ -668,6 +732,10 @@ module ActiveRecord
668
732
  thread
669
733
  end
670
734
 
735
+ def current_thread
736
+ @lock_thread || Thread.current
737
+ end
738
+
671
739
  # Take control of all existing connections so a "group" action such as
672
740
  # reload/disconnect can be performed safely. It is no longer enough to
673
741
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -686,13 +754,13 @@ module ActiveRecord
686
754
  end
687
755
 
688
756
  newly_checked_out = []
689
- timeout_time = Time.now + (@checkout_timeout * 2)
757
+ timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2)
690
758
 
691
759
  @available.with_a_bias_for(Thread.current) do
692
760
  loop do
693
761
  synchronize do
694
762
  return if collected_conns.size == @connections.size && @now_connecting == 0
695
- remaining_timeout = timeout_time - Time.now
763
+ remaining_timeout = timeout_time - Concurrent.monotonic_time
696
764
  remaining_timeout = 0 if remaining_timeout < 0
697
765
  conn = checkout_for_exclusive_access(remaining_timeout)
698
766
  collected_conns << conn
@@ -809,7 +877,7 @@ module ActiveRecord
809
877
 
810
878
  def new_connection
811
879
  Base.send(spec.adapter_method, spec.config).tap do |conn|
812
- conn.schema_cache = schema_cache.dup if schema_cache
880
+ conn.check_version
813
881
  end
814
882
  end
815
883
 
@@ -915,6 +983,16 @@ module ActiveRecord
915
983
  # about the model. The model needs to pass a specification name to the handler,
916
984
  # in order to look up the correct connection pool.
917
985
  class ConnectionHandler
986
+ def self.create_owner_to_pool # :nodoc:
987
+ Concurrent::Map.new(initial_capacity: 2) do |h, k|
988
+ # Discard the parent's connection pools immediately; we have no need
989
+ # of them
990
+ discard_unowned_pools(h)
991
+
992
+ h[k] = Concurrent::Map.new(initial_capacity: 2)
993
+ end
994
+ end
995
+
918
996
  def self.unowned_pool_finalizer(pid_map) # :nodoc:
919
997
  lambda do |_|
920
998
  discard_unowned_pools(pid_map)
@@ -929,19 +1007,33 @@ module ActiveRecord
929
1007
 
930
1008
  def initialize
931
1009
  # 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
1010
+ @owner_to_pool = ConnectionHandler.create_owner_to_pool
939
1011
 
940
1012
  # Backup finalizer: if the forked child never needed a pool, the above
941
1013
  # early discard has not occurred
942
1014
  ObjectSpace.define_finalizer self, ConnectionHandler.unowned_pool_finalizer(@owner_to_pool)
943
1015
  end
944
1016
 
1017
+ def prevent_writes # :nodoc:
1018
+ Thread.current[:prevent_writes]
1019
+ end
1020
+
1021
+ def prevent_writes=(prevent_writes) # :nodoc:
1022
+ Thread.current[:prevent_writes] = prevent_writes
1023
+ end
1024
+
1025
+ # Prevent writing to the database regardless of role.
1026
+ #
1027
+ # In some cases you may want to prevent writes to the database
1028
+ # even if you are on a database that can write. `while_preventing_writes`
1029
+ # will prevent writes to the database for the duration of the block.
1030
+ def while_preventing_writes(enabled = true)
1031
+ original, self.prevent_writes = self.prevent_writes, enabled
1032
+ yield
1033
+ ensure
1034
+ self.prevent_writes = original
1035
+ end
1036
+
945
1037
  def connection_pool_list
946
1038
  owner_to_pool.values.compact
947
1039
  end
@@ -1006,7 +1098,16 @@ module ActiveRecord
1006
1098
  # for (not necessarily the current class).
1007
1099
  def retrieve_connection(spec_name) #:nodoc:
1008
1100
  pool = retrieve_connection_pool(spec_name)
1009
- raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." unless pool
1101
+
1102
+ unless pool
1103
+ # multiple database application
1104
+ if ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
1105
+ raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
1106
+ else
1107
+ raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found."
1108
+ end
1109
+ end
1110
+
1010
1111
  pool.connection
1011
1112
  end
1012
1113
 
@@ -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.
@@ -173,6 +176,23 @@ module ActiveRecord
173
176
  exec_delete(sql, name, binds)
174
177
  end
175
178
 
179
+ # Executes the truncate statement.
180
+ def truncate(table_name, name = nil)
181
+ execute(build_truncate_statements(table_name), name)
182
+ end
183
+
184
+ def truncate_tables(*table_names) # :nodoc:
185
+ return if table_names.empty?
186
+
187
+ with_multi_statements do
188
+ disable_referential_integrity do
189
+ Array(build_truncate_statements(*table_names)).each do |sql|
190
+ execute_batch(sql, "Truncate Tables")
191
+ end
192
+ end
193
+ end
194
+ end
195
+
176
196
  # Runs the given block in a database transaction, and returns the result
177
197
  # of the block.
178
198
  #
@@ -333,46 +353,20 @@ module ActiveRecord
333
353
  # We keep this method to provide fallback
334
354
  # for databases like sqlite that do not support bulk inserts.
335
355
  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"
356
+ execute(build_fixture_sql(Array.wrap(fixture), table_name), "Fixture Insert")
359
357
  end
360
358
 
361
359
  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?
360
+ fixture_inserts = build_fixture_statements(fixture_set)
361
+ table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
362
+ total_sql = Array(combine_multi_statements(table_deletes + fixture_inserts))
363
+
364
+ with_multi_statements do
365
+ disable_referential_integrity do
366
+ transaction(requires_new: true) do
367
+ total_sql.each do |sql|
368
+ execute_batch(sql, "Fixtures Load")
369
+ end
376
370
  end
377
371
  end
378
372
  end
@@ -396,15 +390,33 @@ module ActiveRecord
396
390
  end
397
391
  end
398
392
 
393
+ # Fixture value is quoted by Arel, however scalar values
394
+ # are not quotable. In this case we want to convert
395
+ # the column value to YAML.
396
+ def with_yaml_fallback(value) # :nodoc:
397
+ if value.is_a?(Hash) || value.is_a?(Array)
398
+ YAML.dump(value)
399
+ else
400
+ value
401
+ end
402
+ end
403
+
399
404
  private
405
+ def execute_batch(sql, name = nil)
406
+ execute(sql, name)
407
+ end
408
+
409
+ DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
410
+ private_constant :DEFAULT_INSERT_VALUE
411
+
400
412
  def default_insert_value(column)
401
- Arel.sql("DEFAULT")
413
+ DEFAULT_INSERT_VALUE
402
414
  end
403
415
 
404
416
  def build_fixture_sql(fixtures, table_name)
405
417
  columns = schema_cache.columns_hash(table_name)
406
418
 
407
- values = fixtures.map do |fixture|
419
+ values_list = fixtures.map do |fixture|
408
420
  fixture = fixture.stringify_keys
409
421
 
410
422
  unknown_columns = fixture.keys - columns.keys
@@ -415,8 +427,7 @@ module ActiveRecord
415
427
  columns.map do |name, column|
416
428
  if fixture.key?(name)
417
429
  type = lookup_cast_type_from_column(column)
418
- bind = Relation::QueryAttribute.new(name, fixture[name], type)
419
- with_yaml_fallback(bind.value_for_database)
430
+ with_yaml_fallback(type.serialize(fixture[name]))
420
431
  else
421
432
  default_insert_value(column)
422
433
  end
@@ -426,12 +437,43 @@ module ActiveRecord
426
437
  table = Arel::Table.new(table_name)
427
438
  manager = Arel::InsertManager.new
428
439
  manager.into(table)
429
- columns.each_key { |column| manager.columns << table[column] }
430
- manager.values = manager.create_values_list(values)
431
440
 
441
+ if values_list.size == 1
442
+ values = values_list.shift
443
+ new_values = []
444
+ columns.each_key.with_index { |column, i|
445
+ unless values[i].equal?(DEFAULT_INSERT_VALUE)
446
+ new_values << values[i]
447
+ manager.columns << table[column]
448
+ end
449
+ }
450
+ values_list << new_values
451
+ else
452
+ columns.each_key { |column| manager.columns << table[column] }
453
+ end
454
+
455
+ manager.values = manager.create_values_list(values_list)
432
456
  manager.to_sql
433
457
  end
434
458
 
459
+ def build_fixture_statements(fixture_set)
460
+ fixture_set.map do |table_name, fixtures|
461
+ next if fixtures.empty?
462
+ build_fixture_sql(fixtures, table_name)
463
+ end.compact
464
+ end
465
+
466
+ def build_truncate_statements(*table_names)
467
+ truncate_tables = table_names.map do |table_name|
468
+ "TRUNCATE TABLE #{quote_table_name(table_name)}"
469
+ end
470
+ combine_multi_statements(truncate_tables)
471
+ end
472
+
473
+ def with_multi_statements
474
+ yield
475
+ end
476
+
435
477
  def combine_multi_statements(total_sql)
436
478
  total_sql.join(";\n")
437
479
  end
@@ -445,7 +487,7 @@ module ActiveRecord
445
487
  exec_query(sql, name, binds, prepare: true)
446
488
  end
447
489
 
448
- def sql_for_insert(sql, pk, sequence_name, binds)
490
+ def sql_for_insert(sql, pk, binds)
449
491
  [sql, binds]
450
492
  end
451
493
 
@@ -465,17 +507,6 @@ module ActiveRecord
465
507
  relation
466
508
  end
467
509
  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
510
  end
480
511
  end
481
512
  end
@@ -7,7 +7,8 @@ module ActiveRecord
7
7
  module QueryCache
8
8
  class << self
9
9
  def included(base) #:nodoc:
10
- dirties_query_cache base, :insert, :update, :delete, :rollback_to_savepoint, :rollback_db_transaction
10
+ dirties_query_cache base, :insert, :update, :delete, :truncate, :truncate_tables,
11
+ :rollback_to_savepoint, :rollback_db_transaction
11
12
 
12
13
  base.set_callback :checkout, :after, :configure_query_cache!
13
14
  base.set_callback :checkin, :after, :disable_query_cache!
@@ -17,7 +18,7 @@ module ActiveRecord
17
18
  method_names.each do |method_name|
18
19
  base.class_eval <<-end_code, __FILE__, __LINE__ + 1
19
20
  def #{method_name}(*)
20
- clear_query_cache if @query_cache_enabled
21
+ ActiveRecord::Base.clear_query_caches_for_current_thread if @query_cache_enabled
21
22
  super
22
23
  end
23
24
  end_code
@@ -32,17 +33,17 @@ module ActiveRecord
32
33
  end
33
34
 
34
35
  def enable_query_cache!
35
- @query_cache_enabled[connection_cache_key(Thread.current)] = true
36
+ @query_cache_enabled[connection_cache_key(current_thread)] = true
36
37
  connection.enable_query_cache! if active_connection?
37
38
  end
38
39
 
39
40
  def disable_query_cache!
40
- @query_cache_enabled.delete connection_cache_key(Thread.current)
41
+ @query_cache_enabled.delete connection_cache_key(current_thread)
41
42
  connection.disable_query_cache! if active_connection?
42
43
  end
43
44
 
44
45
  def query_cache_enabled
45
- @query_cache_enabled[connection_cache_key(Thread.current)]
46
+ @query_cache_enabled[connection_cache_key(current_thread)]
46
47
  end
47
48
  end
48
49
 
@@ -96,6 +97,11 @@ module ActiveRecord
96
97
  if @query_cache_enabled && !locked?(arel)
97
98
  arel = arel_from_relation(arel)
98
99
  sql, binds = to_sql_and_binds(arel, binds)
100
+
101
+ if preparable.nil?
102
+ preparable = prepared_statements ? visitor.preparable : false
103
+ end
104
+
99
105
  cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) }
100
106
  else
101
107
  super
@@ -138,15 +138,72 @@ module ActiveRecord
138
138
  "'#{quote_string(value.to_s)}'"
139
139
  end
140
140
 
141
- def type_casted_binds(binds) # :nodoc:
142
- if binds.first.is_a?(Array)
143
- binds.map { |column, value| type_cast(value, column) }
144
- else
145
- binds.map { |attr| type_cast(attr.value_for_database) }
146
- end
141
+ def sanitize_as_sql_comment(value) # :nodoc:
142
+ value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
143
+ end
144
+
145
+ def column_name_matcher # :nodoc:
146
+ COLUMN_NAME
147
+ end
148
+
149
+ def column_name_with_order_matcher # :nodoc:
150
+ COLUMN_NAME_WITH_ORDER
147
151
  end
148
152
 
153
+ # Regexp for column names (with or without a table name prefix).
154
+ # Matches the following:
155
+ #
156
+ # "#{table_name}.#{column_name}"
157
+ # "#{column_name}"
158
+ COLUMN_NAME = /
159
+ \A
160
+ (
161
+ (?:
162
+ # table_name.column_name | function(one or no argument)
163
+ ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
164
+ )
165
+ (?:\s+AS\s+\w+)?
166
+ )
167
+ (?:\s*,\s*\g<1>)*
168
+ \z
169
+ /ix
170
+
171
+ # Regexp for column names with order (with or without a table name prefix,
172
+ # with or without various order modifiers). Matches the following:
173
+ #
174
+ # "#{table_name}.#{column_name}"
175
+ # "#{table_name}.#{column_name} #{direction}"
176
+ # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
177
+ # "#{table_name}.#{column_name} NULLS LAST"
178
+ # "#{column_name}"
179
+ # "#{column_name} #{direction}"
180
+ # "#{column_name} #{direction} NULLS FIRST"
181
+ # "#{column_name} NULLS LAST"
182
+ COLUMN_NAME_WITH_ORDER = /
183
+ \A
184
+ (
185
+ (?:
186
+ # table_name.column_name | function(one or no argument)
187
+ ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
188
+ )
189
+ (?:\s+ASC|\s+DESC)?
190
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
191
+ )
192
+ (?:\s*,\s*\g<1>)*
193
+ \z
194
+ /ix
195
+
196
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
197
+
149
198
  private
199
+ def type_casted_binds(binds)
200
+ if binds.first.is_a?(Array)
201
+ binds.map { |column, value| type_cast(value, column) }
202
+ else
203
+ binds.map { |attr| type_cast(attr.value_for_database) }
204
+ end
205
+ end
206
+
150
207
  def lookup_cast_type(sql_type)
151
208
  type_map.lookup(sql_type)
152
209
  end