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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +455 -9
- data/README.rdoc +3 -1
- data/lib/active_record/associations/association.rb +18 -1
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +5 -2
- data/lib/active_record/associations/builder/collection_association.rb +5 -15
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_association.rb +5 -6
- data/lib/active_record/associations/collection_proxy.rb +13 -42
- data/lib/active_record/associations/has_many_association.rb +1 -9
- data/lib/active_record/associations/has_many_through_association.rb +4 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
- data/lib/active_record/associations/join_dependency.rb +10 -9
- data/lib/active_record/associations/preloader/association.rb +37 -34
- data/lib/active_record/associations/preloader/through_association.rb +48 -39
- data/lib/active_record/associations/preloader.rb +11 -6
- data/lib/active_record/associations.rb +3 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +47 -14
- data/lib/active_record/attribute_methods/primary_key.rb +7 -15
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +3 -9
- data/lib/active_record/attribute_methods/write.rb +6 -12
- data/lib/active_record/attribute_methods.rb +3 -53
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +15 -5
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/callbacks.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +124 -23
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +101 -70
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +11 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
- data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
- data/lib/active_record/connection_adapters/abstract_adapter.rb +108 -39
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +93 -134
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +1 -1
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +91 -24
- data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +69 -118
- data/lib/active_record/connection_handling.rb +32 -16
- data/lib/active_record/core.rb +27 -20
- data/lib/active_record/database_configurations/hash_config.rb +11 -11
- data/lib/active_record/database_configurations/url_config.rb +21 -16
- data/lib/active_record/database_configurations.rb +99 -50
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +15 -0
- data/lib/active_record/errors.rb +18 -13
- data/lib/active_record/fixtures.rb +11 -6
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +13 -1
- data/lib/active_record/internal_metadata.rb +5 -1
- data/lib/active_record/locking/optimistic.rb +3 -4
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +72 -63
- data/lib/active_record/migration.rb +62 -44
- data/lib/active_record/persistence.rb +212 -19
- data/lib/active_record/querying.rb +18 -14
- data/lib/active_record/railtie.rb +9 -1
- data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
- data/lib/active_record/railties/databases.rake +124 -25
- data/lib/active_record/reflection.rb +18 -32
- data/lib/active_record/relation/calculations.rb +40 -44
- data/lib/active_record/relation/delegation.rb +23 -31
- data/lib/active_record/relation/finder_methods.rb +13 -13
- data/lib/active_record/relation/merger.rb +11 -16
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +217 -68
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +10 -10
- data/lib/active_record/relation.rb +184 -35
- data/lib/active_record/sanitization.rb +33 -4
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +10 -1
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/scoping/default.rb +7 -15
- data/lib/active_record/scoping/named.rb +10 -2
- data/lib/active_record/scoping.rb +6 -7
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/store.rb +48 -0
- data/lib/active_record/table_metadata.rb +9 -13
- data/lib/active_record/tasks/database_tasks.rb +109 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
- data/lib/active_record/test_databases.rb +1 -16
- data/lib/active_record/test_fixtures.rb +2 -2
- data/lib/active_record/timestamp.rb +35 -19
- data/lib/active_record/touch_later.rb +4 -2
- data/lib/active_record/transactions.rb +55 -45
- data/lib/active_record/type_caster/connection.rb +16 -10
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record.rb +7 -1
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes/and.rb +1 -1
- data/lib/arel/nodes/case.rb +1 -1
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/select_core.rb +16 -12
- data/lib/arel/nodes/unary.rb +1 -0
- data/lib/arel/nodes/values_list.rb +2 -17
- data/lib/arel/nodes.rb +2 -1
- data/lib/arel/select_manager.rb +10 -10
- data/lib/arel/visitors/depth_first.rb +7 -2
- data/lib/arel/visitors/dot.rb +7 -2
- data/lib/arel/visitors/ibm_db.rb +13 -0
- data/lib/arel/visitors/informix.rb +6 -0
- data/lib/arel/visitors/mssql.rb +15 -1
- data/lib/arel/visitors/oracle12.rb +4 -5
- data/lib/arel/visitors/postgresql.rb +4 -10
- data/lib/arel/visitors/to_sql.rb +107 -131
- data/lib/arel/visitors/visitor.rb +9 -5
- data/lib/arel.rb +7 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +17 -13
- data/lib/active_record/collection_cache_key.rb +0 -53
- 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 =
|
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 =
|
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
|
-
|
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, :
|
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(
|
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(
|
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 =
|
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 -
|
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.
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
24
|
-
|
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
|
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,
|
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
|
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
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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, :
|
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
|
-
|
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(
|
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(
|
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(
|
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
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|