activerecord 7.1.5.1 → 8.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +369 -2484
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +21 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +92 -295
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +63 -49
- data/lib/active_record/autosave_association.rb +92 -57
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
- data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -12
- data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
- data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +36 -20
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
- data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +65 -0
- data/lib/active_record/connection_handling.rb +74 -37
- data/lib/active_record/core.rb +132 -51
- data/lib/active_record/counter_cache.rb +19 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +23 -4
- data/lib/active_record/database_configurations/hash_config.rb +46 -34
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +41 -17
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +7 -7
- data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
- data/lib/active_record/encryption/encryptor.rb +28 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +20 -16
- data/lib/active_record/errors.rb +54 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -33
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +19 -16
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/log_subscriber.rb +5 -32
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +33 -14
- data/lib/active_record/migration/compatibility.rb +8 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +104 -98
- data/lib/active_record/model_schema.rb +32 -70
- data/lib/active_record/nested_attributes.rb +15 -9
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +127 -451
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +104 -37
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +24 -12
- data/lib/active_record/railtie.rb +26 -68
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +43 -61
- data/lib/active_record/reflection.rb +112 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +138 -72
- data/lib/active_record/relation/calculations.rb +122 -82
- data/lib/active_record/relation/delegation.rb +30 -22
- data/lib/active_record/relation/finder_methods.rb +32 -18
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +16 -3
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +317 -101
- data/lib/active_record/relation/spawn_methods.rb +3 -19
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +561 -119
- data/lib/active_record/result.rb +95 -46
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +31 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +53 -20
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/signed_id.rb +24 -4
- data/lib/active_record/statement_cache.rb +19 -19
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +2 -13
- data/lib/active_record/tasks/database_tasks.rb +87 -58
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
- data/lib/active_record/test_fixtures.rb +98 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +72 -17
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +138 -57
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +5 -4
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +18 -16
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
data/lib/active_record/result.rb
CHANGED
@@ -8,7 +8,7 @@ module ActiveRecord
|
|
8
8
|
# {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
|
9
9
|
# on any database connection adapter. For example:
|
10
10
|
#
|
11
|
-
# result = ActiveRecord::Base.
|
11
|
+
# result = ActiveRecord::Base.lease_connection.exec_query('SELECT id, title, body FROM posts')
|
12
12
|
# result # => #<ActiveRecord::Result:0xdeadbeef>
|
13
13
|
#
|
14
14
|
# # Get the column names of the result:
|
@@ -36,6 +36,59 @@ module ActiveRecord
|
|
36
36
|
class Result
|
37
37
|
include Enumerable
|
38
38
|
|
39
|
+
class IndexedRow
|
40
|
+
def initialize(column_indexes, row)
|
41
|
+
@column_indexes = column_indexes
|
42
|
+
@row = row
|
43
|
+
end
|
44
|
+
|
45
|
+
def size
|
46
|
+
@column_indexes.size
|
47
|
+
end
|
48
|
+
alias_method :length, :size
|
49
|
+
|
50
|
+
def each_key(&block)
|
51
|
+
@column_indexes.each_key(&block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def keys
|
55
|
+
@column_indexes.keys
|
56
|
+
end
|
57
|
+
|
58
|
+
def ==(other)
|
59
|
+
if other.is_a?(Hash)
|
60
|
+
to_hash == other
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def key?(column)
|
67
|
+
@column_indexes.key?(column)
|
68
|
+
end
|
69
|
+
|
70
|
+
def fetch(column)
|
71
|
+
if index = @column_indexes[column]
|
72
|
+
@row[index]
|
73
|
+
elsif block_given?
|
74
|
+
yield
|
75
|
+
else
|
76
|
+
raise KeyError, "key not found: #{column.inspect}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def [](column)
|
81
|
+
if index = @column_indexes[column]
|
82
|
+
@row[index]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_h
|
87
|
+
@column_indexes.transform_values { |index| @row[index] }
|
88
|
+
end
|
89
|
+
alias_method :to_hash, :to_h
|
90
|
+
end
|
91
|
+
|
39
92
|
attr_reader :columns, :rows, :column_types
|
40
93
|
|
41
94
|
def self.empty(async: false) # :nodoc:
|
@@ -46,11 +99,14 @@ module ActiveRecord
|
|
46
99
|
end
|
47
100
|
end
|
48
101
|
|
49
|
-
def initialize(columns, rows, column_types =
|
50
|
-
|
102
|
+
def initialize(columns, rows, column_types = nil)
|
103
|
+
# We freeze the strings to prevent them getting duped when
|
104
|
+
# used as keys in ActiveRecord::Base's @attributes hash
|
105
|
+
@columns = columns.each(&:-@).freeze
|
51
106
|
@rows = rows
|
52
107
|
@hash_rows = nil
|
53
|
-
@column_types = column_types
|
108
|
+
@column_types = column_types || EMPTY_HASH
|
109
|
+
@column_indexes = nil
|
54
110
|
end
|
55
111
|
|
56
112
|
# Returns true if this result set includes the column named +name+
|
@@ -64,7 +120,9 @@ module ActiveRecord
|
|
64
120
|
end
|
65
121
|
|
66
122
|
# Calls the given block once for each element in row collection, passing
|
67
|
-
# row as parameter.
|
123
|
+
# row as parameter. Each row is a Hash-like, read only object.
|
124
|
+
#
|
125
|
+
# To get real hashes, use +.to_a.each+.
|
68
126
|
#
|
69
127
|
# Returns an +Enumerator+ if no block is given.
|
70
128
|
def each(&block)
|
@@ -131,17 +189,37 @@ module ActiveRecord
|
|
131
189
|
end
|
132
190
|
|
133
191
|
def initialize_copy(other)
|
134
|
-
@
|
135
|
-
@rows = rows.dup
|
192
|
+
@rows = rows.dup
|
136
193
|
@column_types = column_types.dup
|
137
194
|
@hash_rows = nil
|
138
195
|
end
|
139
196
|
|
140
197
|
def freeze # :nodoc:
|
141
198
|
hash_rows.freeze
|
199
|
+
indexed_rows.freeze
|
142
200
|
super
|
143
201
|
end
|
144
202
|
|
203
|
+
def column_indexes # :nodoc:
|
204
|
+
@column_indexes ||= begin
|
205
|
+
index = 0
|
206
|
+
hash = {}
|
207
|
+
length = columns.length
|
208
|
+
while index < length
|
209
|
+
hash[columns[index]] = index
|
210
|
+
index += 1
|
211
|
+
end
|
212
|
+
hash.freeze
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def indexed_rows # :nodoc:
|
217
|
+
@indexed_rows ||= begin
|
218
|
+
columns = column_indexes
|
219
|
+
@rows.map { |row| IndexedRow.new(columns, row) }.freeze
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
145
223
|
private
|
146
224
|
def column_type(name, index, type_overrides)
|
147
225
|
type_overrides.fetch(name) do
|
@@ -152,47 +230,18 @@ module ActiveRecord
|
|
152
230
|
end
|
153
231
|
|
154
232
|
def hash_rows
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
length = columns.length
|
161
|
-
template = nil
|
162
|
-
|
163
|
-
@rows.map { |row|
|
164
|
-
if template
|
165
|
-
# We use transform_values to build subsequent rows from the
|
166
|
-
# hash of the first row. This is faster because we avoid any
|
167
|
-
# reallocs and in Ruby 2.7+ avoid hashing entirely.
|
168
|
-
index = -1
|
169
|
-
template.transform_values do
|
170
|
-
row[index += 1]
|
171
|
-
end
|
172
|
-
else
|
173
|
-
# In the past we used Hash[columns.zip(row)]
|
174
|
-
# though elegant, the verbose way is much more efficient
|
175
|
-
# both time and memory wise cause it avoids a big array allocation
|
176
|
-
# this method is called a lot and needs to be micro optimised
|
177
|
-
hash = {}
|
178
|
-
|
179
|
-
index = 0
|
180
|
-
while index < length
|
181
|
-
hash[columns[index]] = row[index]
|
182
|
-
index += 1
|
183
|
-
end
|
184
|
-
|
185
|
-
# It's possible to select the same column twice, in which case
|
186
|
-
# we can't use a template
|
187
|
-
template = hash if hash.length == length
|
188
|
-
|
189
|
-
hash
|
190
|
-
end
|
191
|
-
}
|
192
|
-
end
|
233
|
+
# We use transform_values to rows.
|
234
|
+
# This is faster because we avoid any reallocs and avoid hashing entirely.
|
235
|
+
@hash_rows ||= @rows.map do |row|
|
236
|
+
column_indexes.transform_values { |index| row[index] }
|
237
|
+
end
|
193
238
|
end
|
194
239
|
|
195
|
-
|
240
|
+
empty_array = [].freeze
|
241
|
+
EMPTY_HASH = {}.freeze
|
242
|
+
private_constant :EMPTY_HASH
|
243
|
+
|
244
|
+
EMPTY = new(empty_array, empty_array, EMPTY_HASH).freeze
|
196
245
|
private_constant :EMPTY
|
197
246
|
|
198
247
|
EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
|
@@ -25,15 +25,54 @@ module ActiveRecord
|
|
25
25
|
ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
|
26
26
|
end
|
27
27
|
|
28
|
+
def queries_count
|
29
|
+
ActiveSupport::IsolatedExecutionState[:active_record_queries_count] ||= 0
|
30
|
+
end
|
31
|
+
|
32
|
+
def queries_count=(count)
|
33
|
+
ActiveSupport::IsolatedExecutionState[:active_record_queries_count] = count
|
34
|
+
end
|
35
|
+
|
36
|
+
def cached_queries_count
|
37
|
+
ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] ||= 0
|
38
|
+
end
|
39
|
+
|
40
|
+
def cached_queries_count=(count)
|
41
|
+
ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] = count
|
42
|
+
end
|
43
|
+
|
28
44
|
def reset
|
45
|
+
reset_runtimes
|
46
|
+
reset_queries_count
|
47
|
+
reset_cached_queries_count
|
48
|
+
end
|
49
|
+
|
50
|
+
def reset_runtimes
|
29
51
|
rt, self.sql_runtime = sql_runtime, 0.0
|
30
52
|
self.async_sql_runtime = 0.0
|
31
53
|
rt
|
32
54
|
end
|
55
|
+
|
56
|
+
def reset_queries_count
|
57
|
+
qc = queries_count
|
58
|
+
self.queries_count = 0
|
59
|
+
qc
|
60
|
+
end
|
61
|
+
|
62
|
+
def reset_cached_queries_count
|
63
|
+
qc = cached_queries_count
|
64
|
+
self.cached_queries_count = 0
|
65
|
+
qc
|
66
|
+
end
|
33
67
|
end
|
34
68
|
end
|
35
69
|
|
36
70
|
ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
|
71
|
+
unless ["SCHEMA", "TRANSACTION"].include?(payload[:name])
|
72
|
+
ActiveRecord::RuntimeRegistry.queries_count += 1
|
73
|
+
ActiveRecord::RuntimeRegistry.cached_queries_count += 1 if payload[:cached]
|
74
|
+
end
|
75
|
+
|
37
76
|
runtime = (finish - start) * 1_000.0
|
38
77
|
|
39
78
|
if payload[:async]
|
@@ -17,7 +17,7 @@ module ActiveRecord
|
|
17
17
|
# sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
|
18
18
|
# # => "name='foo''bar' and group_id='4'"
|
19
19
|
#
|
20
|
-
# This method will NOT sanitize
|
20
|
+
# This method will NOT sanitize an SQL string since it won't contain
|
21
21
|
# any conditions in it and will return the string as is.
|
22
22
|
#
|
23
23
|
# sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
|
@@ -52,7 +52,7 @@ module ActiveRecord
|
|
52
52
|
# Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
|
53
53
|
# # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
|
54
54
|
#
|
55
|
-
# This method will NOT sanitize
|
55
|
+
# This method will NOT sanitize an SQL string since it won't contain
|
56
56
|
# any conditions in it and will return the string as is.
|
57
57
|
#
|
58
58
|
# sanitize_sql_for_assignment("name=NULL and group_id='4'")
|
@@ -85,7 +85,7 @@ module ActiveRecord
|
|
85
85
|
if condition.is_a?(Array) && condition.first.to_s.include?("?")
|
86
86
|
disallow_raw_sql!(
|
87
87
|
[condition.first],
|
88
|
-
permit:
|
88
|
+
permit: adapter_class.column_name_with_order_matcher
|
89
89
|
)
|
90
90
|
|
91
91
|
# Ensure we aren't dealing with a subclass of String that might
|
@@ -105,12 +105,13 @@ module ActiveRecord
|
|
105
105
|
# sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
|
106
106
|
# # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
|
107
107
|
def sanitize_sql_hash_for_assignment(attrs, table)
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
108
|
+
with_connection do |c|
|
109
|
+
attrs.map do |attr, value|
|
110
|
+
type = type_for_attribute(attr)
|
111
|
+
value = type.serialize(type.cast(value))
|
112
|
+
"#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
|
113
|
+
end.join(", ")
|
114
|
+
end
|
114
115
|
end
|
115
116
|
|
116
117
|
# Sanitizes a +string+ so that it is safe to use within an SQL
|
@@ -163,17 +164,23 @@ module ActiveRecord
|
|
163
164
|
def sanitize_sql_array(ary)
|
164
165
|
statement, *values = ary
|
165
166
|
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
|
166
|
-
|
167
|
+
with_connection do |c|
|
168
|
+
replace_named_bind_variables(c, statement, values.first)
|
169
|
+
end
|
167
170
|
elsif statement.include?("?")
|
168
|
-
|
171
|
+
with_connection do |c|
|
172
|
+
replace_bind_variables(c, statement, values)
|
173
|
+
end
|
169
174
|
elsif statement.blank?
|
170
175
|
statement
|
171
176
|
else
|
172
|
-
|
177
|
+
with_connection do |c|
|
178
|
+
statement % values.collect { |value| c.quote_string(value.to_s) }
|
179
|
+
end
|
173
180
|
end
|
174
181
|
end
|
175
182
|
|
176
|
-
def disallow_raw_sql!(args, permit:
|
183
|
+
def disallow_raw_sql!(args, permit: adapter_class.column_name_matcher) # :nodoc:
|
177
184
|
unexpected = nil
|
178
185
|
args.each do |arg|
|
179
186
|
next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s.strip)
|
@@ -193,48 +200,47 @@ module ActiveRecord
|
|
193
200
|
end
|
194
201
|
|
195
202
|
private
|
196
|
-
def replace_bind_variables(statement, values)
|
203
|
+
def replace_bind_variables(connection, statement, values)
|
197
204
|
raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
|
198
205
|
bound = values.dup
|
199
|
-
c = connection
|
200
206
|
statement.gsub(/\?/) do
|
201
|
-
replace_bind_variable(bound.shift
|
207
|
+
replace_bind_variable(connection, bound.shift)
|
202
208
|
end
|
203
209
|
end
|
204
210
|
|
205
|
-
def replace_bind_variable(
|
211
|
+
def replace_bind_variable(connection, value)
|
206
212
|
if ActiveRecord::Relation === value
|
207
213
|
value.to_sql
|
208
214
|
else
|
209
|
-
quote_bound_value(
|
215
|
+
quote_bound_value(connection, value)
|
210
216
|
end
|
211
217
|
end
|
212
218
|
|
213
|
-
def replace_named_bind_variables(statement, bind_vars)
|
219
|
+
def replace_named_bind_variables(connection, statement, bind_vars)
|
214
220
|
statement.gsub(/([:\\]?):([a-zA-Z]\w*)/) do |match|
|
215
|
-
if $1 == ":" # skip
|
221
|
+
if $1 == ":" # skip PostgreSQL casts
|
216
222
|
match # return the whole match
|
217
223
|
elsif $1 == "\\" # escaped literal colon
|
218
224
|
match[1..-1] # return match with escaping backlash char removed
|
219
225
|
elsif bind_vars.include?(match = $2.to_sym)
|
220
|
-
replace_bind_variable(bind_vars[match])
|
226
|
+
replace_bind_variable(connection, bind_vars[match])
|
221
227
|
else
|
222
228
|
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
|
223
229
|
end
|
224
230
|
end
|
225
231
|
end
|
226
232
|
|
227
|
-
def quote_bound_value(
|
233
|
+
def quote_bound_value(connection, value)
|
228
234
|
if value.respond_to?(:map) && !value.acts_like?(:string)
|
229
235
|
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
230
236
|
if values.empty?
|
231
|
-
|
237
|
+
connection.quote(connection.cast_bound_value(nil))
|
232
238
|
else
|
233
|
-
values.map! { |v|
|
239
|
+
values.map! { |v| connection.quote(connection.cast_bound_value(v)) }.join(",")
|
234
240
|
end
|
235
241
|
else
|
236
242
|
value = value.id_for_database if value.respond_to?(:id_for_database)
|
237
|
-
|
243
|
+
connection.quote(connection.cast_bound_value(value))
|
238
244
|
end
|
239
245
|
end
|
240
246
|
|
data/lib/active_record/schema.rb
CHANGED
@@ -52,14 +52,16 @@ module ActiveRecord
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def define(info, &block) # :nodoc:
|
55
|
-
|
55
|
+
connection_pool.with_connection do |connection|
|
56
|
+
instance_eval(&block)
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
58
|
+
connection_pool.schema_migration.create_table
|
59
|
+
if info[:version].present?
|
60
|
+
connection.assume_migrated_upto_version(info[:version])
|
61
|
+
end
|
61
62
|
|
62
|
-
|
63
|
+
connection_pool.internal_metadata.create_table_and_set_flags(connection_pool.migration_context.current_environment)
|
64
|
+
end
|
63
65
|
end
|
64
66
|
end
|
65
67
|
|
@@ -41,8 +41,10 @@ module ActiveRecord
|
|
41
41
|
cattr_accessor :unique_ignore_pattern, default: /^uniq_rails_[0-9a-f]{10}$/
|
42
42
|
|
43
43
|
class << self
|
44
|
-
def dump(
|
45
|
-
connection
|
44
|
+
def dump(pool = ActiveRecord::Base.connection_pool, stream = $stdout, config = ActiveRecord::Base)
|
45
|
+
pool.with_connection do |connection|
|
46
|
+
connection.create_schema_dumper(generate_options(config)).dump(stream)
|
47
|
+
end
|
46
48
|
stream
|
47
49
|
end
|
48
50
|
|
@@ -61,6 +63,7 @@ module ActiveRecord
|
|
61
63
|
extensions(stream)
|
62
64
|
types(stream)
|
63
65
|
tables(stream)
|
66
|
+
virtual_tables(stream)
|
64
67
|
trailer(stream)
|
65
68
|
stream
|
66
69
|
end
|
@@ -70,7 +73,7 @@ module ActiveRecord
|
|
70
73
|
|
71
74
|
def initialize(connection, options = {})
|
72
75
|
@connection = connection
|
73
|
-
@version = connection.migration_context.current_version rescue nil
|
76
|
+
@version = connection.pool.migration_context.current_version rescue nil
|
74
77
|
@options = options
|
75
78
|
@ignore_tables = [
|
76
79
|
ActiveRecord::Base.schema_migrations_table_name,
|
@@ -124,18 +127,31 @@ module ActiveRecord
|
|
124
127
|
def schemas(stream)
|
125
128
|
end
|
126
129
|
|
130
|
+
# virtual tables are only supported by SQLite
|
131
|
+
def virtual_tables(stream)
|
132
|
+
end
|
133
|
+
|
127
134
|
def tables(stream)
|
128
135
|
sorted_tables = @connection.tables.sort
|
129
136
|
|
130
|
-
sorted_tables.
|
131
|
-
|
137
|
+
not_ignored_tables = sorted_tables.reject { |table_name| ignored?(table_name) }
|
138
|
+
|
139
|
+
not_ignored_tables.each_with_index do |table_name, index|
|
140
|
+
table(table_name, stream)
|
141
|
+
stream.puts if index < not_ignored_tables.count - 1
|
132
142
|
end
|
133
143
|
|
134
144
|
# dump foreign keys at the end to make sure all dependent tables exist.
|
135
|
-
if @connection.
|
136
|
-
|
137
|
-
|
145
|
+
if @connection.supports_foreign_keys?
|
146
|
+
foreign_keys_stream = StringIO.new
|
147
|
+
not_ignored_tables.each do |tbl|
|
148
|
+
foreign_keys(tbl, foreign_keys_stream)
|
138
149
|
end
|
150
|
+
|
151
|
+
foreign_keys_string = foreign_keys_stream.string
|
152
|
+
stream.puts if foreign_keys_string.length > 0
|
153
|
+
|
154
|
+
stream.print foreign_keys_string
|
139
155
|
end
|
140
156
|
end
|
141
157
|
|
@@ -191,12 +207,16 @@ module ActiveRecord
|
|
191
207
|
end
|
192
208
|
|
193
209
|
indexes_in_create(table, tbl)
|
194
|
-
check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
|
210
|
+
remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
|
195
211
|
exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
|
196
212
|
unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
|
197
213
|
|
198
214
|
tbl.puts " end"
|
199
|
-
|
215
|
+
|
216
|
+
if remaining
|
217
|
+
tbl.puts
|
218
|
+
tbl.print remaining.string
|
219
|
+
end
|
200
220
|
|
201
221
|
stream.print tbl.string
|
202
222
|
rescue => e
|
@@ -262,24 +282,37 @@ module ActiveRecord
|
|
262
282
|
|
263
283
|
def check_constraints_in_create(table, stream)
|
264
284
|
if (check_constraints = @connection.check_constraints(table)).any?
|
265
|
-
|
266
|
-
parts = [
|
267
|
-
"t.check_constraint #{check_constraint.expression.inspect}"
|
268
|
-
]
|
285
|
+
check_valid, check_invalid = check_constraints.partition { |chk| chk.validate? }
|
269
286
|
|
270
|
-
|
271
|
-
|
287
|
+
unless check_valid.empty?
|
288
|
+
check_constraint_statements = check_valid.map do |check|
|
289
|
+
" t.check_constraint #{check_parts(check).join(', ')}"
|
272
290
|
end
|
273
291
|
|
274
|
-
|
275
|
-
|
276
|
-
" #{parts.join(', ')}"
|
292
|
+
stream.puts check_constraint_statements.sort.join("\n")
|
277
293
|
end
|
278
294
|
|
279
|
-
|
295
|
+
unless check_invalid.empty?
|
296
|
+
remaining = StringIO.new
|
297
|
+
table_name = remove_prefix_and_suffix(table).inspect
|
298
|
+
|
299
|
+
add_check_constraint_statements = check_invalid.map do |check|
|
300
|
+
" add_check_constraint #{([table_name] + check_parts(check)).join(', ')}"
|
301
|
+
end
|
302
|
+
|
303
|
+
remaining.puts add_check_constraint_statements.sort.join("\n")
|
304
|
+
remaining
|
305
|
+
end
|
280
306
|
end
|
281
307
|
end
|
282
308
|
|
309
|
+
def check_parts(check)
|
310
|
+
check_parts = [ check.expression.inspect ]
|
311
|
+
check_parts << "name: #{check.name.inspect}" if check.export_name_on_schema_dump?
|
312
|
+
check_parts << "validate: #{check.validate?.inspect}" unless check.validate?
|
313
|
+
check_parts
|
314
|
+
end
|
315
|
+
|
283
316
|
def foreign_keys(table, stream)
|
284
317
|
if (foreign_keys = @connection.foreign_keys(table)).any?
|
285
318
|
add_foreign_key_statements = foreign_keys.map do |foreign_key|
|
@@ -9,29 +9,36 @@ module ActiveRecord
|
|
9
9
|
class NullSchemaMigration # :nodoc:
|
10
10
|
end
|
11
11
|
|
12
|
-
attr_reader :
|
12
|
+
attr_reader :arel_table
|
13
13
|
|
14
|
-
def initialize(
|
15
|
-
@
|
14
|
+
def initialize(pool)
|
15
|
+
@pool = pool
|
16
16
|
@arel_table = Arel::Table.new(table_name)
|
17
17
|
end
|
18
18
|
|
19
19
|
def create_version(version)
|
20
20
|
im = Arel::InsertManager.new(arel_table)
|
21
21
|
im.insert(arel_table[primary_key] => version)
|
22
|
-
|
22
|
+
@pool.with_connection do |connection|
|
23
|
+
connection.insert(im, "#{self.class} Create", primary_key, version)
|
24
|
+
end
|
23
25
|
end
|
24
26
|
|
25
27
|
def delete_version(version)
|
26
28
|
dm = Arel::DeleteManager.new(arel_table)
|
27
29
|
dm.wheres = [arel_table[primary_key].eq(version)]
|
28
30
|
|
29
|
-
|
31
|
+
@pool.with_connection do |connection|
|
32
|
+
connection.delete(dm, "#{self.class} Destroy")
|
33
|
+
end
|
30
34
|
end
|
31
35
|
|
32
36
|
def delete_all_versions
|
33
|
-
|
34
|
-
|
37
|
+
# Eagerly check in connection to avoid checking in/out many times in the called method.
|
38
|
+
@pool.with_connection do
|
39
|
+
versions.each do |version|
|
40
|
+
delete_version(version)
|
41
|
+
end
|
35
42
|
end
|
36
43
|
end
|
37
44
|
|
@@ -44,15 +51,19 @@ module ActiveRecord
|
|
44
51
|
end
|
45
52
|
|
46
53
|
def create_table
|
47
|
-
|
48
|
-
connection.
|
49
|
-
|
54
|
+
@pool.with_connection do |connection|
|
55
|
+
unless connection.table_exists?(table_name)
|
56
|
+
connection.create_table(table_name, id: false) do |t|
|
57
|
+
t.string :version, **connection.internal_string_options_for_primary_key
|
58
|
+
end
|
50
59
|
end
|
51
60
|
end
|
52
61
|
end
|
53
62
|
|
54
63
|
def drop_table
|
55
|
-
|
64
|
+
@pool.with_connection do |connection|
|
65
|
+
connection.drop_table table_name, if_exists: true
|
66
|
+
end
|
56
67
|
end
|
57
68
|
|
58
69
|
def normalize_migration_number(number)
|
@@ -68,7 +79,9 @@ module ActiveRecord
|
|
68
79
|
sm.project(arel_table[primary_key])
|
69
80
|
sm.order(arel_table[primary_key].asc)
|
70
81
|
|
71
|
-
|
82
|
+
@pool.with_connection do |connection|
|
83
|
+
connection.select_values(sm, "#{self.class} Load")
|
84
|
+
end
|
72
85
|
end
|
73
86
|
|
74
87
|
def integer_versions
|
@@ -79,11 +92,15 @@ module ActiveRecord
|
|
79
92
|
sm = Arel::SelectManager.new(arel_table)
|
80
93
|
sm.project(*Arel::Nodes::Count.new([Arel.star]))
|
81
94
|
|
82
|
-
|
95
|
+
@pool.with_connection do |connection|
|
96
|
+
connection.select_values(sm, "#{self.class} Count").first
|
97
|
+
end
|
83
98
|
end
|
84
99
|
|
85
100
|
def table_exists?
|
86
|
-
connection
|
101
|
+
@pool.with_connection do |connection|
|
102
|
+
connection.data_source_exists?(table_name)
|
103
|
+
end
|
87
104
|
end
|
88
105
|
end
|
89
106
|
end
|
@@ -23,7 +23,7 @@ module ActiveRecord
|
|
23
23
|
scope = current_scope
|
24
24
|
|
25
25
|
if scope
|
26
|
-
if self == scope.
|
26
|
+
if self == scope.model
|
27
27
|
scope.clone
|
28
28
|
else
|
29
29
|
relation.merge!(scope)
|
@@ -190,7 +190,11 @@ module ActiveRecord
|
|
190
190
|
|
191
191
|
private
|
192
192
|
def singleton_method_added(name)
|
193
|
-
|
193
|
+
super
|
194
|
+
# Most Kernel extends are both singleton and instance methods so
|
195
|
+
# respond_to is a fast check, but we don't want to define methods
|
196
|
+
# only on the module (ex. Module#name)
|
197
|
+
generate_relation_method(name) if Kernel.respond_to?(name) && (Kernel.method_defined?(name) || Kernel.private_method_defined?(name)) && !ActiveRecord::Relation.method_defined?(name)
|
194
198
|
end
|
195
199
|
end
|
196
200
|
end
|