activerecord 7.0.0 → 7.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 +515 -1268
- data/MIT-LICENSE +1 -1
- data/README.rdoc +31 -31
- 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 +28 -17
- data/lib/active_record/associations/collection_proxy.rb +36 -13
- 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 +28 -18
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +18 -14
- 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 +2 -4
- 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 +378 -491
- data/lib/active_record/attribute_assignment.rb +1 -13
- 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 +153 -70
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +153 -40
- data/lib/active_record/attributes.rb +63 -48
- data/lib/active_record/autosave_association.rb +70 -38
- data/lib/active_record/base.rb +12 -8
- data/lib/active_record/callbacks.rb +16 -32
- 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 -34
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
- 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 +297 -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 +215 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
- 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 +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
- 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 +27 -140
- data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
- 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 +16 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- 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 +4 -2
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- 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 +379 -66
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
- 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 +61 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -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 +98 -106
- data/lib/active_record/core.rb +220 -177
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
- 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 +88 -35
- data/lib/active_record/delegated_type.rb +40 -11
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/disable_joins_association_relation.rb +1 -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 +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +47 -25
- data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
- data/lib/active_record/encryption/encryptor.rb +25 -10
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
- 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/message.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 +4 -4
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +23 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +131 -27
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- data/lib/active_record/explain_subscriber.rb +1 -1
- 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 +169 -99
- 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 +13 -10
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +39 -24
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +28 -27
- data/lib/active_record/marshalling.rb +56 -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 +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +110 -13
- data/lib/active_record/migration/compatibility.rb +174 -64
- 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 +292 -125
- data/lib/active_record/model_schema.rb +113 -112
- data/lib/active_record/nested_attributes.rb +35 -9
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +177 -345
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +19 -25
- data/lib/active_record/query_logs.rb +102 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +34 -9
- data/lib/active_record/railtie.rb +153 -100
- data/lib/active_record/railties/controller_runtime.rb +24 -10
- data/lib/active_record/railties/databases.rake +148 -152
- 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 +278 -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 +293 -108
- data/lib/active_record/relation/delegation.rb +31 -20
- 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 +38 -4
- 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 +25 -1
- data/lib/active_record/relation/query_methods.rb +625 -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 +602 -96
- data/lib/active_record/result.rb +55 -52
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +76 -30
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +82 -30
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +20 -12
- 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/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +29 -8
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +191 -121
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_fixtures.rb +174 -152
- 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 +109 -27
- data/lib/active_record/translation.rb +1 -3
- 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 +9 -7
- 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 +12 -6
- 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 +63 -14
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +266 -30
- 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/filter_predications.rb +1 -1
- 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/filter.rb +1 -1
- 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} +9 -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/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
|
|
@@ -108,7 +114,7 @@ module ActiveRecord
|
|
|
108
114
|
type = if type_overrides.is_a?(Array)
|
|
109
115
|
type_overrides.first
|
|
110
116
|
else
|
|
111
|
-
column_type(columns.first, type_overrides)
|
|
117
|
+
column_type(columns.first, 0, type_overrides)
|
|
112
118
|
end
|
|
113
119
|
|
|
114
120
|
rows.map do |(value)|
|
|
@@ -118,7 +124,7 @@ module ActiveRecord
|
|
|
118
124
|
types = if type_overrides.is_a?(Array)
|
|
119
125
|
type_overrides
|
|
120
126
|
else
|
|
121
|
-
columns.map { |name| column_type(name, type_overrides) }
|
|
127
|
+
columns.map.with_index { |name, i| column_type(name, i, type_overrides) }
|
|
122
128
|
end
|
|
123
129
|
|
|
124
130
|
rows.map do |values|
|
|
@@ -128,58 +134,55 @@ 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
|
-
def column_type(name, type_overrides
|
|
162
|
+
def column_type(name, index, type_overrides)
|
|
139
163
|
type_overrides.fetch(name) do
|
|
140
|
-
column_types.fetch(
|
|
164
|
+
column_types.fetch(index) do
|
|
165
|
+
column_types.fetch(name, Type.default_value)
|
|
166
|
+
end
|
|
141
167
|
end
|
|
142
168
|
end
|
|
143
169
|
|
|
144
170
|
def hash_rows
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
length = columns.length
|
|
151
|
-
template = nil
|
|
152
|
-
|
|
153
|
-
@rows.map { |row|
|
|
154
|
-
if template
|
|
155
|
-
# We use transform_values to build subsequent rows from the
|
|
156
|
-
# hash of the first row. This is faster because we avoid any
|
|
157
|
-
# reallocs and in Ruby 2.7+ avoid hashing entirely.
|
|
158
|
-
index = -1
|
|
159
|
-
template.transform_values do
|
|
160
|
-
row[index += 1]
|
|
161
|
-
end
|
|
162
|
-
else
|
|
163
|
-
# In the past we used Hash[columns.zip(row)]
|
|
164
|
-
# though elegant, the verbose way is much more efficient
|
|
165
|
-
# both time and memory wise cause it avoids a big array allocation
|
|
166
|
-
# this method is called a lot and needs to be micro optimised
|
|
167
|
-
hash = {}
|
|
168
|
-
|
|
169
|
-
index = 0
|
|
170
|
-
while index < length
|
|
171
|
-
hash[columns[index]] = row[index]
|
|
172
|
-
index += 1
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# It's possible to select the same column twice, in which case
|
|
176
|
-
# we can't use a template
|
|
177
|
-
template = hash if hash.length == length
|
|
178
|
-
|
|
179
|
-
hash
|
|
180
|
-
end
|
|
181
|
-
}
|
|
182
|
-
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
|
|
183
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
|
|
184
187
|
end
|
|
185
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
|
|
@@ -92,26 +114,32 @@ module ActiveRecord
|
|
|
92
114
|
end
|
|
93
115
|
|
|
94
116
|
# Sanitizes a +string+ so that it is safe to use within an SQL
|
|
95
|
-
# LIKE statement. This method uses +escape_character+ to escape all
|
|
117
|
+
# LIKE statement. This method uses +escape_character+ to escape all
|
|
118
|
+
# occurrences of itself, "_" and "%".
|
|
96
119
|
#
|
|
97
|
-
# sanitize_sql_like("100%")
|
|
98
|
-
# # => "100\\%"
|
|
120
|
+
# sanitize_sql_like("100% true!")
|
|
121
|
+
# # => "100\\% true!"
|
|
99
122
|
#
|
|
100
123
|
# sanitize_sql_like("snake_cased_string")
|
|
101
124
|
# # => "snake\\_cased\\_string"
|
|
102
125
|
#
|
|
103
|
-
# sanitize_sql_like("100%", "!")
|
|
104
|
-
# # => "100!%"
|
|
126
|
+
# sanitize_sql_like("100% true!", "!")
|
|
127
|
+
# # => "100!% true!!"
|
|
105
128
|
#
|
|
106
129
|
# sanitize_sql_like("snake_cased_string", "!")
|
|
107
130
|
# # => "snake!_cased!_string"
|
|
108
131
|
def sanitize_sql_like(string, escape_character = "\\")
|
|
109
|
-
|
|
110
|
-
|
|
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)
|
|
111
137
|
end
|
|
112
138
|
|
|
113
139
|
# Accepts an array of conditions. The array has each value
|
|
114
|
-
# 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.
|
|
115
143
|
#
|
|
116
144
|
# sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
|
|
117
145
|
# # => "name='foo''bar' and group_id=4"
|
|
@@ -119,22 +147,39 @@ module ActiveRecord
|
|
|
119
147
|
# sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
|
|
120
148
|
# # => "name='foo''bar' and group_id=4"
|
|
121
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
|
+
#
|
|
122
153
|
# sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
|
|
123
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'"
|
|
124
163
|
def sanitize_sql_array(ary)
|
|
125
164
|
statement, *values = ary
|
|
126
165
|
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
|
|
127
|
-
|
|
166
|
+
with_connection do |c|
|
|
167
|
+
replace_named_bind_variables(c, statement, values.first)
|
|
168
|
+
end
|
|
128
169
|
elsif statement.include?("?")
|
|
129
|
-
|
|
170
|
+
with_connection do |c|
|
|
171
|
+
replace_bind_variables(c, statement, values)
|
|
172
|
+
end
|
|
130
173
|
elsif statement.blank?
|
|
131
174
|
statement
|
|
132
175
|
else
|
|
133
|
-
|
|
176
|
+
with_connection do |c|
|
|
177
|
+
statement % values.collect { |value| c.quote_string(value.to_s) }
|
|
178
|
+
end
|
|
134
179
|
end
|
|
135
180
|
end
|
|
136
181
|
|
|
137
|
-
def disallow_raw_sql!(args, permit:
|
|
182
|
+
def disallow_raw_sql!(args, permit: adapter_class.column_name_matcher) # :nodoc:
|
|
138
183
|
unexpected = nil
|
|
139
184
|
args.each do |arg|
|
|
140
185
|
next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s.strip)
|
|
@@ -154,46 +199,47 @@ module ActiveRecord
|
|
|
154
199
|
end
|
|
155
200
|
|
|
156
201
|
private
|
|
157
|
-
def replace_bind_variables(statement, values)
|
|
202
|
+
def replace_bind_variables(connection, statement, values)
|
|
158
203
|
raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
|
|
159
204
|
bound = values.dup
|
|
160
|
-
c = connection
|
|
161
205
|
statement.gsub(/\?/) do
|
|
162
|
-
replace_bind_variable(bound.shift
|
|
206
|
+
replace_bind_variable(connection, bound.shift)
|
|
163
207
|
end
|
|
164
208
|
end
|
|
165
209
|
|
|
166
|
-
def replace_bind_variable(
|
|
210
|
+
def replace_bind_variable(connection, value)
|
|
167
211
|
if ActiveRecord::Relation === value
|
|
168
212
|
value.to_sql
|
|
169
213
|
else
|
|
170
|
-
quote_bound_value(
|
|
214
|
+
quote_bound_value(connection, value)
|
|
171
215
|
end
|
|
172
216
|
end
|
|
173
217
|
|
|
174
|
-
def replace_named_bind_variables(statement, bind_vars)
|
|
175
|
-
statement.gsub(/(
|
|
176
|
-
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
|
|
177
221
|
match # return the whole match
|
|
222
|
+
elsif $1 == "\\" # escaped literal colon
|
|
223
|
+
match[1..-1] # return match with escaping backlash char removed
|
|
178
224
|
elsif bind_vars.include?(match = $2.to_sym)
|
|
179
|
-
replace_bind_variable(bind_vars[match])
|
|
225
|
+
replace_bind_variable(connection, bind_vars[match])
|
|
180
226
|
else
|
|
181
227
|
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
|
|
182
228
|
end
|
|
183
229
|
end
|
|
184
230
|
end
|
|
185
231
|
|
|
186
|
-
def quote_bound_value(
|
|
232
|
+
def quote_bound_value(connection, value)
|
|
187
233
|
if value.respond_to?(:map) && !value.acts_like?(:string)
|
|
188
234
|
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
|
189
235
|
if values.empty?
|
|
190
|
-
|
|
236
|
+
connection.quote(connection.cast_bound_value(nil))
|
|
191
237
|
else
|
|
192
|
-
values.map! { |v|
|
|
238
|
+
values.map! { |v| connection.quote(connection.cast_bound_value(v)) }.join(",")
|
|
193
239
|
end
|
|
194
240
|
else
|
|
195
241
|
value = value.id_for_database if value.respond_to?(:id_for_database)
|
|
196
|
-
|
|
242
|
+
connection.quote(connection.cast_bound_value(value))
|
|
197
243
|
end
|
|
198
244
|
end
|
|
199
245
|
|
data/lib/active_record/schema.rb
CHANGED
|
@@ -10,7 +10,7 @@ module ActiveRecord
|
|
|
10
10
|
#
|
|
11
11
|
# Usage:
|
|
12
12
|
#
|
|
13
|
-
# ActiveRecord::Schema.define do
|
|
13
|
+
# ActiveRecord::Schema[7.0].define do
|
|
14
14
|
# create_table :authors do |t|
|
|
15
15
|
# t.string :name, null: false
|
|
16
16
|
# end
|
|
@@ -30,32 +30,48 @@ module ActiveRecord
|
|
|
30
30
|
# ActiveRecord::Schema is only supported by database adapters that also
|
|
31
31
|
# support migrations, the two features being very similar.
|
|
32
32
|
class Schema < Migration::Current
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
33
|
+
module Definition
|
|
34
|
+
extend ActiveSupport::Concern
|
|
35
|
+
|
|
36
|
+
module ClassMethods
|
|
37
|
+
# Eval the given block. All methods available to the current connection
|
|
38
|
+
# adapter are available within the block, so you can easily use the
|
|
39
|
+
# database definition DSL to build up your schema (
|
|
40
|
+
# {create_table}[rdoc-ref:ConnectionAdapters::SchemaStatements#create_table],
|
|
41
|
+
# {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index], etc.).
|
|
42
|
+
#
|
|
43
|
+
# The +info+ hash is optional, and if given is used to define metadata
|
|
44
|
+
# about the current schema (currently, only the schema's version):
|
|
45
|
+
#
|
|
46
|
+
# ActiveRecord::Schema[7.0].define(version: 2038_01_19_000001) do
|
|
47
|
+
# ...
|
|
48
|
+
# end
|
|
49
|
+
def define(info = {}, &block)
|
|
50
|
+
new.define(info, &block)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def define(info, &block) # :nodoc:
|
|
55
|
+
connection_pool.with_connection do |connection|
|
|
56
|
+
instance_eval(&block)
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
connection_pool.schema_migration.create_table
|
|
59
|
+
if info[:version].present?
|
|
60
|
+
connection.assume_migrated_upto_version(info[:version])
|
|
61
|
+
end
|
|
51
62
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
connection.assume_migrated_upto_version(info[:version])
|
|
63
|
+
connection_pool.internal_metadata.create_table_and_set_flags(connection_pool.migration_context.current_environment)
|
|
64
|
+
end
|
|
55
65
|
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
include Definition
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
def self.[](version)
|
|
71
|
+
@class_for_version ||= {}
|
|
72
|
+
@class_for_version[version] ||= Class.new(Migration::Compatibility.find(version)) do
|
|
73
|
+
include Definition
|
|
74
|
+
end
|
|
59
75
|
end
|
|
60
76
|
end
|
|
61
77
|
end
|