activerecord 7.0.8.1 → 7.2.2.1
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 +642 -1925
- data/MIT-LICENSE +1 -1
- data/README.rdoc +29 -29
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +23 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- 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/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +26 -14
- data/lib/active_record/associations/collection_proxy.rb +29 -11
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +21 -14
- data/lib/active_record/associations/has_many_through_association.rb +17 -7
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- 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/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +354 -485
- data/lib/active_record/attribute_assignment.rb +0 -4
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +131 -32
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +148 -33
- data/lib/active_record/attributes.rb +64 -50
- data/lib/active_record/autosave_association.rb +69 -37
- data/lib/active_record/base.rb +9 -5
- data/lib/active_record/callbacks.rb +11 -25
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
- data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
- data/lib/active_record/connection_adapters.rb +124 -1
- data/lib/active_record/connection_handling.rb +96 -104
- data/lib/active_record/core.rb +251 -176
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +39 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +45 -21
- data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- 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 +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +129 -28
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +167 -97
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +11 -8
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +18 -22
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +106 -8
- data/lib/active_record/migration/compatibility.rb +147 -5
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +234 -117
- data/lib/active_record/model_schema.rb +90 -102
- data/lib/active_record/nested_attributes.rb +48 -11
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +168 -339
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -25
- data/lib/active_record/query_logs.rb +92 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +33 -8
- data/lib/active_record/railtie.rb +129 -85
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +145 -154
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +267 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +250 -93
- data/lib/active_record/relation/delegation.rb +30 -19
- data/lib/active_record/relation/finder_methods.rb +93 -18
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +28 -16
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +576 -107
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +580 -90
- data/lib/active_record/result.rb +49 -48
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +70 -25
- data/lib/active_record/schema.rb +8 -7
- data/lib/active_record/schema_dumper.rb +63 -14
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +3 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +27 -6
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +190 -118
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_fixtures.rb +170 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +106 -24
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +61 -11
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +247 -33
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +112 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +59 -17
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
data/lib/active_record/result.rb
CHANGED
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
###
|
5
|
+
# = Active Record \Result
|
6
|
+
#
|
5
7
|
# This class encapsulates a result returned from calling
|
6
8
|
# {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
|
7
9
|
# on any database connection adapter. For example:
|
8
10
|
#
|
9
|
-
# result = ActiveRecord::Base.
|
11
|
+
# result = ActiveRecord::Base.lease_connection.exec_query('SELECT id, title, body FROM posts')
|
10
12
|
# result # => #<ActiveRecord::Result:0xdeadbeef>
|
11
13
|
#
|
12
14
|
# # Get the column names of the result:
|
@@ -36,20 +38,24 @@ module ActiveRecord
|
|
36
38
|
|
37
39
|
attr_reader :columns, :rows, :column_types
|
38
40
|
|
39
|
-
def self.empty # :nodoc:
|
40
|
-
|
41
|
+
def self.empty(async: false) # :nodoc:
|
42
|
+
if async
|
43
|
+
EMPTY_ASYNC
|
44
|
+
else
|
45
|
+
EMPTY
|
46
|
+
end
|
41
47
|
end
|
42
48
|
|
43
|
-
def initialize(columns, rows, column_types =
|
44
|
-
|
49
|
+
def initialize(columns, rows, column_types = nil)
|
50
|
+
# We freeze the strings to prevent them getting duped when
|
51
|
+
# used as keys in ActiveRecord::Base's @attributes hash
|
52
|
+
@columns = columns.each(&:-@).freeze
|
45
53
|
@rows = rows
|
46
54
|
@hash_rows = nil
|
47
|
-
@column_types = column_types
|
55
|
+
@column_types = column_types || EMPTY_HASH
|
56
|
+
@column_indexes = nil
|
48
57
|
end
|
49
58
|
|
50
|
-
EMPTY = new([].freeze, [].freeze, {}.freeze)
|
51
|
-
private_constant :EMPTY
|
52
|
-
|
53
59
|
# Returns true if this result set includes the column named +name+
|
54
60
|
def includes_column?(name)
|
55
61
|
@columns.include? name
|
@@ -128,12 +134,30 @@ module ActiveRecord
|
|
128
134
|
end
|
129
135
|
|
130
136
|
def initialize_copy(other)
|
131
|
-
@columns = columns
|
137
|
+
@columns = columns
|
132
138
|
@rows = rows.dup
|
133
139
|
@column_types = column_types.dup
|
134
140
|
@hash_rows = nil
|
135
141
|
end
|
136
142
|
|
143
|
+
def freeze # :nodoc:
|
144
|
+
hash_rows.freeze
|
145
|
+
super
|
146
|
+
end
|
147
|
+
|
148
|
+
def column_indexes # :nodoc:
|
149
|
+
@column_indexes ||= begin
|
150
|
+
index = 0
|
151
|
+
hash = {}
|
152
|
+
length = columns.length
|
153
|
+
while index < length
|
154
|
+
hash[columns[index]] = index
|
155
|
+
index += 1
|
156
|
+
end
|
157
|
+
hash
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
137
161
|
private
|
138
162
|
def column_type(name, index, type_overrides)
|
139
163
|
type_overrides.fetch(name) do
|
@@ -144,44 +168,21 @@ module ActiveRecord
|
|
144
168
|
end
|
145
169
|
|
146
170
|
def hash_rows
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
length = columns.length
|
153
|
-
template = nil
|
154
|
-
|
155
|
-
@rows.map { |row|
|
156
|
-
if template
|
157
|
-
# We use transform_values to build subsequent rows from the
|
158
|
-
# hash of the first row. This is faster because we avoid any
|
159
|
-
# reallocs and in Ruby 2.7+ avoid hashing entirely.
|
160
|
-
index = -1
|
161
|
-
template.transform_values do
|
162
|
-
row[index += 1]
|
163
|
-
end
|
164
|
-
else
|
165
|
-
# In the past we used Hash[columns.zip(row)]
|
166
|
-
# though elegant, the verbose way is much more efficient
|
167
|
-
# both time and memory wise cause it avoids a big array allocation
|
168
|
-
# this method is called a lot and needs to be micro optimised
|
169
|
-
hash = {}
|
170
|
-
|
171
|
-
index = 0
|
172
|
-
while index < length
|
173
|
-
hash[columns[index]] = row[index]
|
174
|
-
index += 1
|
175
|
-
end
|
176
|
-
|
177
|
-
# It's possible to select the same column twice, in which case
|
178
|
-
# we can't use a template
|
179
|
-
template = hash if hash.length == length
|
180
|
-
|
181
|
-
hash
|
182
|
-
end
|
183
|
-
}
|
184
|
-
end
|
171
|
+
# We use transform_values to rows.
|
172
|
+
# This is faster because we avoid any reallocs and avoid hashing entirely.
|
173
|
+
@hash_rows ||= @rows.map do |row|
|
174
|
+
column_indexes.transform_values { |index| row[index] }
|
175
|
+
end
|
185
176
|
end
|
177
|
+
|
178
|
+
empty_array = [].freeze
|
179
|
+
EMPTY_HASH = {}.freeze
|
180
|
+
private_constant :EMPTY_HASH
|
181
|
+
|
182
|
+
EMPTY = new(empty_array, empty_array, EMPTY_HASH).freeze
|
183
|
+
private_constant :EMPTY
|
184
|
+
|
185
|
+
EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
|
186
|
+
private_constant :EMPTY_ASYNC
|
186
187
|
end
|
187
188
|
end
|
@@ -10,11 +10,73 @@ module ActiveRecord
|
|
10
10
|
extend self
|
11
11
|
|
12
12
|
def sql_runtime
|
13
|
-
ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime]
|
13
|
+
ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] ||= 0.0
|
14
14
|
end
|
15
15
|
|
16
16
|
def sql_runtime=(runtime)
|
17
17
|
ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
|
18
18
|
end
|
19
|
+
|
20
|
+
def async_sql_runtime
|
21
|
+
ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
|
22
|
+
end
|
23
|
+
|
24
|
+
def async_sql_runtime=(runtime)
|
25
|
+
ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
|
26
|
+
end
|
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
|
+
|
44
|
+
def reset
|
45
|
+
reset_runtimes
|
46
|
+
reset_queries_count
|
47
|
+
reset_cached_queries_count
|
48
|
+
end
|
49
|
+
|
50
|
+
def reset_runtimes
|
51
|
+
rt, self.sql_runtime = sql_runtime, 0.0
|
52
|
+
self.async_sql_runtime = 0.0
|
53
|
+
rt
|
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
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
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
|
+
|
76
|
+
runtime = (finish - start) * 1_000.0
|
77
|
+
|
78
|
+
if payload[:async]
|
79
|
+
ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
|
19
80
|
end
|
81
|
+
ActiveRecord::RuntimeRegistry.sql_runtime += runtime
|
20
82
|
end
|
@@ -5,8 +5,8 @@ module ActiveRecord
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
module ClassMethods
|
8
|
-
# Accepts an array
|
9
|
-
#
|
8
|
+
# Accepts an array of SQL conditions and sanitizes them into a valid
|
9
|
+
# SQL fragment for a WHERE clause.
|
10
10
|
#
|
11
11
|
# sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
|
12
12
|
# # => "name='foo''bar' and group_id=4"
|
@@ -17,8 +17,19 @@ 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 an SQL string since it won't contain
|
21
|
+
# any conditions in it and will return the string as is.
|
22
|
+
#
|
20
23
|
# sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
|
21
24
|
# # => "name='foo''bar' and group_id='4'"
|
25
|
+
#
|
26
|
+
# Note that this sanitization method is not schema-aware, hence won't do any type casting
|
27
|
+
# and will directly use the database adapter's +quote+ method.
|
28
|
+
# For MySQL specifically this means that numeric parameters will be quoted as strings
|
29
|
+
# to prevent query manipulation attacks.
|
30
|
+
#
|
31
|
+
# sanitize_sql_for_conditions(["role = ?", 0])
|
32
|
+
# # => "role = '0'"
|
22
33
|
def sanitize_sql_for_conditions(condition)
|
23
34
|
return nil if condition.blank?
|
24
35
|
|
@@ -29,8 +40,8 @@ module ActiveRecord
|
|
29
40
|
end
|
30
41
|
alias :sanitize_sql :sanitize_sql_for_conditions
|
31
42
|
|
32
|
-
# Accepts an array
|
33
|
-
#
|
43
|
+
# Accepts an array or hash of SQL conditions and sanitizes them into
|
44
|
+
# a valid SQL fragment for a SET clause.
|
34
45
|
#
|
35
46
|
# sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
|
36
47
|
# # => "name=NULL and group_id=4"
|
@@ -41,8 +52,19 @@ module ActiveRecord
|
|
41
52
|
# Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
|
42
53
|
# # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
|
43
54
|
#
|
55
|
+
# This method will NOT sanitize an SQL string since it won't contain
|
56
|
+
# any conditions in it and will return the string as is.
|
57
|
+
#
|
44
58
|
# sanitize_sql_for_assignment("name=NULL and group_id='4'")
|
45
59
|
# # => "name=NULL and group_id='4'"
|
60
|
+
#
|
61
|
+
# Note that this sanitization method is not schema-aware, hence won't do any type casting
|
62
|
+
# and will directly use the database adapter's +quote+ method.
|
63
|
+
# For MySQL specifically this means that numeric parameters will be quoted as strings
|
64
|
+
# to prevent query manipulation attacks.
|
65
|
+
#
|
66
|
+
# sanitize_sql_for_assignment(["role = ?", 0])
|
67
|
+
# # => "role = '0'"
|
46
68
|
def sanitize_sql_for_assignment(assignments, default_table_name = table_name)
|
47
69
|
case assignments
|
48
70
|
when Array; sanitize_sql_array(assignments)
|
@@ -63,7 +85,7 @@ module ActiveRecord
|
|
63
85
|
if condition.is_a?(Array) && condition.first.to_s.include?("?")
|
64
86
|
disallow_raw_sql!(
|
65
87
|
[condition.first],
|
66
|
-
permit:
|
88
|
+
permit: adapter_class.column_name_with_order_matcher
|
67
89
|
)
|
68
90
|
|
69
91
|
# Ensure we aren't dealing with a subclass of String that might
|
@@ -107,12 +129,17 @@ module ActiveRecord
|
|
107
129
|
# sanitize_sql_like("snake_cased_string", "!")
|
108
130
|
# # => "snake!_cased!_string"
|
109
131
|
def sanitize_sql_like(string, escape_character = "\\")
|
110
|
-
|
111
|
-
|
132
|
+
if string.include?(escape_character) && escape_character != "%" && escape_character != "_"
|
133
|
+
string = string.gsub(escape_character, '\0\0')
|
134
|
+
end
|
135
|
+
|
136
|
+
string.gsub(/(?=[%_])/, escape_character)
|
112
137
|
end
|
113
138
|
|
114
139
|
# Accepts an array of conditions. The array has each value
|
115
|
-
# sanitized and interpolated into the SQL statement.
|
140
|
+
# sanitized and interpolated into the SQL statement. If using named bind
|
141
|
+
# variables in SQL statements where a colon is required verbatim use a
|
142
|
+
# backslash to escape.
|
116
143
|
#
|
117
144
|
# sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
|
118
145
|
# # => "name='foo''bar' and group_id=4"
|
@@ -120,22 +147,39 @@ module ActiveRecord
|
|
120
147
|
# sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
|
121
148
|
# # => "name='foo''bar' and group_id=4"
|
122
149
|
#
|
150
|
+
# sanitize_sql_array(["TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12\\:MI\\:SS')", date: "foo"])
|
151
|
+
# # => "TO_TIMESTAMP('foo', 'YYYY/MM/DD HH12:MI:SS')"
|
152
|
+
#
|
123
153
|
# sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
|
124
154
|
# # => "name='foo''bar' and group_id='4'"
|
155
|
+
#
|
156
|
+
# Note that this sanitization method is not schema-aware, hence won't do any type casting
|
157
|
+
# and will directly use the database adapter's +quote+ method.
|
158
|
+
# For MySQL specifically this means that numeric parameters will be quoted as strings
|
159
|
+
# to prevent query manipulation attacks.
|
160
|
+
#
|
161
|
+
# sanitize_sql_array(["role = ?", 0])
|
162
|
+
# # => "role = '0'"
|
125
163
|
def sanitize_sql_array(ary)
|
126
164
|
statement, *values = ary
|
127
165
|
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
|
128
|
-
|
166
|
+
with_connection do |c|
|
167
|
+
replace_named_bind_variables(c, statement, values.first)
|
168
|
+
end
|
129
169
|
elsif statement.include?("?")
|
130
|
-
|
170
|
+
with_connection do |c|
|
171
|
+
replace_bind_variables(c, statement, values)
|
172
|
+
end
|
131
173
|
elsif statement.blank?
|
132
174
|
statement
|
133
175
|
else
|
134
|
-
|
176
|
+
with_connection do |c|
|
177
|
+
statement % values.collect { |value| c.quote_string(value.to_s) }
|
178
|
+
end
|
135
179
|
end
|
136
180
|
end
|
137
181
|
|
138
|
-
def disallow_raw_sql!(args, permit:
|
182
|
+
def disallow_raw_sql!(args, permit: adapter_class.column_name_matcher) # :nodoc:
|
139
183
|
unexpected = nil
|
140
184
|
args.each do |arg|
|
141
185
|
next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s.strip)
|
@@ -155,46 +199,47 @@ module ActiveRecord
|
|
155
199
|
end
|
156
200
|
|
157
201
|
private
|
158
|
-
def replace_bind_variables(statement, values)
|
202
|
+
def replace_bind_variables(connection, statement, values)
|
159
203
|
raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
|
160
204
|
bound = values.dup
|
161
|
-
c = connection
|
162
205
|
statement.gsub(/\?/) do
|
163
|
-
replace_bind_variable(bound.shift
|
206
|
+
replace_bind_variable(connection, bound.shift)
|
164
207
|
end
|
165
208
|
end
|
166
209
|
|
167
|
-
def replace_bind_variable(
|
210
|
+
def replace_bind_variable(connection, value)
|
168
211
|
if ActiveRecord::Relation === value
|
169
212
|
value.to_sql
|
170
213
|
else
|
171
|
-
quote_bound_value(
|
214
|
+
quote_bound_value(connection, value)
|
172
215
|
end
|
173
216
|
end
|
174
217
|
|
175
|
-
def replace_named_bind_variables(statement, bind_vars)
|
176
|
-
statement.gsub(/(
|
177
|
-
if $1 == ":" # skip
|
218
|
+
def replace_named_bind_variables(connection, statement, bind_vars)
|
219
|
+
statement.gsub(/([:\\]?):([a-zA-Z]\w*)/) do |match|
|
220
|
+
if $1 == ":" # skip PostgreSQL casts
|
178
221
|
match # return the whole match
|
222
|
+
elsif $1 == "\\" # escaped literal colon
|
223
|
+
match[1..-1] # return match with escaping backlash char removed
|
179
224
|
elsif bind_vars.include?(match = $2.to_sym)
|
180
|
-
replace_bind_variable(bind_vars[match])
|
225
|
+
replace_bind_variable(connection, bind_vars[match])
|
181
226
|
else
|
182
227
|
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
|
183
228
|
end
|
184
229
|
end
|
185
230
|
end
|
186
231
|
|
187
|
-
def quote_bound_value(
|
232
|
+
def quote_bound_value(connection, value)
|
188
233
|
if value.respond_to?(:map) && !value.acts_like?(:string)
|
189
234
|
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
190
235
|
if values.empty?
|
191
|
-
|
236
|
+
connection.quote(connection.cast_bound_value(nil))
|
192
237
|
else
|
193
|
-
values.map! { |v|
|
238
|
+
values.map! { |v| connection.quote(connection.cast_bound_value(v)) }.join(",")
|
194
239
|
end
|
195
240
|
else
|
196
241
|
value = value.id_for_database if value.respond_to?(:id_for_database)
|
197
|
-
|
242
|
+
connection.quote(connection.cast_bound_value(value))
|
198
243
|
end
|
199
244
|
end
|
200
245
|
|
data/lib/active_record/schema.rb
CHANGED
@@ -52,15 +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
|
-
|
63
|
+
connection_pool.internal_metadata.create_table_and_set_flags(connection_pool.migration_context.current_environment)
|
64
|
+
end
|
64
65
|
end
|
65
66
|
end
|
66
67
|
|
@@ -13,8 +13,7 @@ module ActiveRecord
|
|
13
13
|
##
|
14
14
|
# :singleton-method:
|
15
15
|
# A list of tables which should not be dumped to the schema.
|
16
|
-
# Acceptable values are strings
|
17
|
-
# Only strings are accepted if ActiveRecord.schema_format == :sql.
|
16
|
+
# Acceptable values are strings and regexps.
|
18
17
|
cattr_accessor :ignore_tables, default: []
|
19
18
|
|
20
19
|
##
|
@@ -29,9 +28,23 @@ module ActiveRecord
|
|
29
28
|
# should not be dumped to db/schema.rb.
|
30
29
|
cattr_accessor :chk_ignore_pattern, default: /^chk_rails_[0-9a-f]{10}$/
|
31
30
|
|
31
|
+
##
|
32
|
+
# :singleton-method:
|
33
|
+
# Specify a custom regular expression matching exclusion constraints which name
|
34
|
+
# should not be dumped to db/schema.rb.
|
35
|
+
cattr_accessor :excl_ignore_pattern, default: /^excl_rails_[0-9a-f]{10}$/
|
36
|
+
|
37
|
+
##
|
38
|
+
# :singleton-method:
|
39
|
+
# Specify a custom regular expression matching unique constraints which name
|
40
|
+
# should not be dumped to db/schema.rb.
|
41
|
+
cattr_accessor :unique_ignore_pattern, default: /^uniq_rails_[0-9a-f]{10}$/
|
42
|
+
|
32
43
|
class << self
|
33
|
-
def dump(
|
34
|
-
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
|
35
48
|
stream
|
36
49
|
end
|
37
50
|
|
@@ -46,6 +59,7 @@ module ActiveRecord
|
|
46
59
|
|
47
60
|
def dump(stream)
|
48
61
|
header(stream)
|
62
|
+
schemas(stream)
|
49
63
|
extensions(stream)
|
50
64
|
types(stream)
|
51
65
|
tables(stream)
|
@@ -58,8 +72,13 @@ module ActiveRecord
|
|
58
72
|
|
59
73
|
def initialize(connection, options = {})
|
60
74
|
@connection = connection
|
61
|
-
@version = connection.migration_context.current_version rescue nil
|
75
|
+
@version = connection.pool.migration_context.current_version rescue nil
|
62
76
|
@options = options
|
77
|
+
@ignore_tables = [
|
78
|
+
ActiveRecord::Base.schema_migrations_table_name,
|
79
|
+
ActiveRecord::Base.internal_metadata_table_name,
|
80
|
+
self.class.ignore_tables
|
81
|
+
].flatten
|
63
82
|
end
|
64
83
|
|
65
84
|
# turns 20170404131909 into "2017_04_04_131909"
|
@@ -103,18 +122,31 @@ module ActiveRecord
|
|
103
122
|
def types(stream)
|
104
123
|
end
|
105
124
|
|
125
|
+
# schemas are only supported by PostgreSQL
|
126
|
+
def schemas(stream)
|
127
|
+
end
|
128
|
+
|
106
129
|
def tables(stream)
|
107
130
|
sorted_tables = @connection.tables.sort
|
108
131
|
|
109
|
-
sorted_tables.
|
110
|
-
|
132
|
+
not_ignored_tables = sorted_tables.reject { |table_name| ignored?(table_name) }
|
133
|
+
|
134
|
+
not_ignored_tables.each_with_index do |table_name, index|
|
135
|
+
table(table_name, stream)
|
136
|
+
stream.puts if index < not_ignored_tables.count - 1
|
111
137
|
end
|
112
138
|
|
113
139
|
# dump foreign keys at the end to make sure all dependent tables exist.
|
114
140
|
if @connection.supports_foreign_keys?
|
115
|
-
|
116
|
-
|
141
|
+
foreign_keys_stream = StringIO.new
|
142
|
+
not_ignored_tables.each do |tbl|
|
143
|
+
foreign_keys(tbl, foreign_keys_stream)
|
117
144
|
end
|
145
|
+
|
146
|
+
foreign_keys_string = foreign_keys_stream.string
|
147
|
+
stream.puts if foreign_keys_string.length > 0
|
148
|
+
|
149
|
+
stream.print foreign_keys_string
|
118
150
|
end
|
119
151
|
end
|
120
152
|
|
@@ -171,12 +203,12 @@ module ActiveRecord
|
|
171
203
|
|
172
204
|
indexes_in_create(table, tbl)
|
173
205
|
check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
|
206
|
+
exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
|
207
|
+
unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
|
174
208
|
|
175
209
|
tbl.puts " end"
|
176
|
-
tbl.puts
|
177
210
|
|
178
|
-
tbl.
|
179
|
-
stream.print tbl.read
|
211
|
+
stream.print tbl.string
|
180
212
|
rescue => e
|
181
213
|
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
182
214
|
stream.puts "# #{e.message}"
|
@@ -201,6 +233,18 @@ module ActiveRecord
|
|
201
233
|
|
202
234
|
def indexes_in_create(table, stream)
|
203
235
|
if (indexes = @connection.indexes(table)).any?
|
236
|
+
if @connection.supports_exclusion_constraints? && (exclusion_constraints = @connection.exclusion_constraints(table)).any?
|
237
|
+
exclusion_constraint_names = exclusion_constraints.collect(&:name)
|
238
|
+
|
239
|
+
indexes = indexes.reject { |index| exclusion_constraint_names.include?(index.name) }
|
240
|
+
end
|
241
|
+
|
242
|
+
if @connection.supports_unique_constraints? && (unique_constraints = @connection.unique_constraints(table)).any?
|
243
|
+
unique_constraint_names = unique_constraints.collect(&:name)
|
244
|
+
|
245
|
+
indexes = indexes.reject { |index| unique_constraint_names.include?(index.name) }
|
246
|
+
end
|
247
|
+
|
204
248
|
index_statements = indexes.map do |index|
|
205
249
|
" t.index #{index_parts(index).join(', ')}"
|
206
250
|
end
|
@@ -219,6 +263,8 @@ module ActiveRecord
|
|
219
263
|
index_parts << "opclass: #{format_index_parts(index.opclasses)}" if index.opclasses.present?
|
220
264
|
index_parts << "where: #{index.where.inspect}" if index.where
|
221
265
|
index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index)
|
266
|
+
index_parts << "include: #{index.include.inspect}" if index.include
|
267
|
+
index_parts << "nulls_not_distinct: #{index.nulls_not_distinct.inspect}" if index.nulls_not_distinct
|
222
268
|
index_parts << "type: #{index.type.inspect}" if index.type
|
223
269
|
index_parts << "comment: #{index.comment.inspect}" if index.comment
|
224
270
|
index_parts
|
@@ -235,6 +281,8 @@ module ActiveRecord
|
|
235
281
|
parts << "name: #{check_constraint.name.inspect}"
|
236
282
|
end
|
237
283
|
|
284
|
+
parts << "validate: #{check_constraint.validate?.inspect}" unless check_constraint.validate?
|
285
|
+
|
238
286
|
" #{parts.join(', ')}"
|
239
287
|
end
|
240
288
|
|
@@ -250,7 +298,7 @@ module ActiveRecord
|
|
250
298
|
remove_prefix_and_suffix(foreign_key.to_table).inspect,
|
251
299
|
]
|
252
300
|
|
253
|
-
if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table)
|
301
|
+
if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table, "id")
|
254
302
|
parts << "column: #{foreign_key.column.inspect}"
|
255
303
|
end
|
256
304
|
|
@@ -265,6 +313,7 @@ module ActiveRecord
|
|
265
313
|
parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
|
266
314
|
parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
|
267
315
|
parts << "deferrable: #{foreign_key.deferrable.inspect}" if foreign_key.deferrable
|
316
|
+
parts << "validate: #{foreign_key.validate?.inspect}" unless foreign_key.validate?
|
268
317
|
|
269
318
|
" #{parts.join(', ')}"
|
270
319
|
end
|
@@ -302,7 +351,7 @@ module ActiveRecord
|
|
302
351
|
end
|
303
352
|
|
304
353
|
def ignored?(table_name)
|
305
|
-
|
354
|
+
@ignore_tables.any? do |ignored|
|
306
355
|
ignored === remove_prefix_and_suffix(table_name)
|
307
356
|
end
|
308
357
|
end
|