activerecord 7.0.8.7 → 7.1.5.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 +1795 -1424
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -16
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +20 -4
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +19 -13
- data/lib/active_record/associations/collection_proxy.rb +15 -10
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- 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 +3 -2
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/preloader/association.rb +31 -7
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +319 -217
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +21 -8
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +145 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +59 -10
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- 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 +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
- 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 +296 -127
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -92
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +244 -121
- 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 +22 -143
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
- 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 +19 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- 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 +74 -40
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- 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/quoting.rb +10 -6
- 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 +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +364 -61
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +211 -81
- 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 +258 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +72 -95
- data/lib/active_record/core.rb +181 -154
- data/lib/active_record/counter_cache.rb +52 -27
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +28 -14
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +15 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- 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 +42 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
- 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/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +112 -28
- data/lib/active_record/errors.rb +112 -18
- data/lib/active_record/explain.rb +23 -3
- 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 +135 -71
- data/lib/active_record/future_result.rb +40 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- 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 +104 -5
- data/lib/active_record/migration/compatibility.rb +145 -5
- data/lib/active_record/migration/default_strategy.rb +23 -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 +219 -111
- data/lib/active_record/model_schema.rb +69 -44
- data/lib/active_record/nested_attributes.rb +37 -8
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +188 -37
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +4 -22
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +107 -45
- data/lib/active_record/railties/controller_runtime.rb +12 -6
- data/lib/active_record/railties/databases.rake +144 -150
- 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 +181 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +187 -63
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- 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 +26 -14
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +371 -68
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +103 -37
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +24 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +46 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -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 +7 -5
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +10 -1
- data/lib/active_record/tasks/database_tasks.rb +152 -108
- 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 +15 -7
- data/lib/active_record/test_fixtures.rb +114 -96
- data/lib/active_record/timestamp.rb +30 -16
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +36 -10
- 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/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- 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 +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +122 -17
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -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/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +5 -1
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +83 -18
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- 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 +46 -10
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -15,7 +15,12 @@ module ActiveRecord
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil) # :nodoc:
|
18
|
+
# Arel::TreeManager -> Arel::Node
|
18
19
|
if arel_or_sql_string.respond_to?(:ast)
|
20
|
+
arel_or_sql_string = arel_or_sql_string.ast
|
21
|
+
end
|
22
|
+
|
23
|
+
if Arel.arel_node?(arel_or_sql_string) && !(String === arel_or_sql_string)
|
19
24
|
unless binds.empty?
|
20
25
|
raise "Passing bind parameters with an arel AST is forbidden. " \
|
21
26
|
"The values must be stored on the AST directly"
|
@@ -25,7 +30,7 @@ module ActiveRecord
|
|
25
30
|
|
26
31
|
if prepared_statements
|
27
32
|
collector.preparable = true
|
28
|
-
sql, binds = visitor.compile(arel_or_sql_string
|
33
|
+
sql, binds = visitor.compile(arel_or_sql_string, collector)
|
29
34
|
|
30
35
|
if binds.length > bind_params_length
|
31
36
|
unprepared_statement do
|
@@ -34,7 +39,7 @@ module ActiveRecord
|
|
34
39
|
end
|
35
40
|
preparable = collector.preparable
|
36
41
|
else
|
37
|
-
sql = visitor.compile(arel_or_sql_string
|
42
|
+
sql = visitor.compile(arel_or_sql_string, collector)
|
38
43
|
end
|
39
44
|
[sql.freeze, binds, preparable]
|
40
45
|
else
|
@@ -65,18 +70,18 @@ module ActiveRecord
|
|
65
70
|
|
66
71
|
select(sql, name, binds, prepare: prepared_statements && preparable, async: async && FutureResult::SelectAll)
|
67
72
|
rescue ::RangeError
|
68
|
-
ActiveRecord::Result.empty
|
73
|
+
ActiveRecord::Result.empty(async: async)
|
69
74
|
end
|
70
75
|
|
71
76
|
# Returns a record hash with the column names as keys and column values
|
72
77
|
# as values.
|
73
|
-
def select_one(arel, name = nil, binds = [])
|
74
|
-
select_all(arel, name, binds).first
|
78
|
+
def select_one(arel, name = nil, binds = [], async: false)
|
79
|
+
select_all(arel, name, binds, async: async).then(&:first)
|
75
80
|
end
|
76
81
|
|
77
82
|
# Returns a single value from a record
|
78
|
-
def select_value(arel, name = nil, binds = [])
|
79
|
-
|
83
|
+
def select_value(arel, name = nil, binds = [], async: false)
|
84
|
+
select_rows(arel, name, binds, async: async).then { |rows| single_value_from_rows(rows) }
|
80
85
|
end
|
81
86
|
|
82
87
|
# Returns an array of the values of the first column in a select:
|
@@ -87,8 +92,8 @@ module ActiveRecord
|
|
87
92
|
|
88
93
|
# Returns an array of arrays containing the field values.
|
89
94
|
# Order is the same as that returned by +columns+.
|
90
|
-
def select_rows(arel, name = nil, binds = [])
|
91
|
-
select_all(arel, name, binds).rows
|
95
|
+
def select_rows(arel, name = nil, binds = [], async: false)
|
96
|
+
select_all(arel, name, binds, async: async).then(&:rows)
|
92
97
|
end
|
93
98
|
|
94
99
|
def query_value(sql, name = nil) # :nodoc:
|
@@ -100,7 +105,7 @@ module ActiveRecord
|
|
100
105
|
end
|
101
106
|
|
102
107
|
def query(sql, name = nil) # :nodoc:
|
103
|
-
|
108
|
+
internal_exec_query(sql, name).rows
|
104
109
|
end
|
105
110
|
|
106
111
|
# Determines whether the SQL statement is a write query.
|
@@ -110,47 +115,63 @@ module ActiveRecord
|
|
110
115
|
|
111
116
|
# Executes the SQL statement in the context of this connection and returns
|
112
117
|
# the raw result from the connection adapter.
|
118
|
+
#
|
119
|
+
# Setting +allow_retry+ to true causes the db to reconnect and retry
|
120
|
+
# executing the SQL statement in case of a connection-related exception.
|
121
|
+
# This option should only be enabled for known idempotent queries.
|
122
|
+
#
|
123
|
+
# Note: the query is assumed to have side effects and the query cache
|
124
|
+
# will be cleared. If the query is read-only, consider using #select_all
|
125
|
+
# instead.
|
126
|
+
#
|
113
127
|
# Note: depending on your database connector, the result returned by this
|
114
|
-
# method may be manually memory managed. Consider using
|
128
|
+
# method may be manually memory managed. Consider using #exec_query
|
115
129
|
# wrapper instead.
|
116
|
-
def execute(sql, name = nil)
|
117
|
-
|
130
|
+
def execute(sql, name = nil, allow_retry: false)
|
131
|
+
internal_execute(sql, name, allow_retry: allow_retry)
|
118
132
|
end
|
119
133
|
|
120
134
|
# Executes +sql+ statement in the context of this connection using
|
121
135
|
# +binds+ as the bind substitutes. +name+ is logged along with
|
122
136
|
# the executed +sql+ statement.
|
137
|
+
#
|
138
|
+
# Note: the query is assumed to have side effects and the query cache
|
139
|
+
# will be cleared. If the query is read-only, consider using #select_all
|
140
|
+
# instead.
|
123
141
|
def exec_query(sql, name = "SQL", binds = [], prepare: false)
|
124
|
-
|
142
|
+
internal_exec_query(sql, name, binds, prepare: prepare)
|
125
143
|
end
|
126
144
|
|
127
145
|
# Executes insert +sql+ statement in the context of this connection using
|
128
146
|
# +binds+ as the bind substitutes. +name+ is logged along with
|
129
147
|
# the executed +sql+ statement.
|
130
|
-
|
131
|
-
|
132
|
-
|
148
|
+
# Some adapters support the `returning` keyword argument which allows to control the result of the query:
|
149
|
+
# `nil` is the default value and maintains default behavior. If an array of column names is passed -
|
150
|
+
# the result will contain values of the specified columns from the inserted row.
|
151
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
|
152
|
+
sql, binds = sql_for_insert(sql, pk, binds, returning)
|
153
|
+
internal_exec_query(sql, name, binds)
|
133
154
|
end
|
134
155
|
|
135
156
|
# Executes delete +sql+ statement in the context of this connection using
|
136
157
|
# +binds+ as the bind substitutes. +name+ is logged along with
|
137
158
|
# the executed +sql+ statement.
|
138
159
|
def exec_delete(sql, name = nil, binds = [])
|
139
|
-
|
160
|
+
internal_exec_query(sql, name, binds)
|
140
161
|
end
|
141
162
|
|
142
163
|
# Executes update +sql+ statement in the context of this connection using
|
143
164
|
# +binds+ as the bind substitutes. +name+ is logged along with
|
144
165
|
# the executed +sql+ statement.
|
145
166
|
def exec_update(sql, name = nil, binds = [])
|
146
|
-
|
167
|
+
internal_exec_query(sql, name, binds)
|
147
168
|
end
|
148
169
|
|
149
170
|
def exec_insert_all(sql, name) # :nodoc:
|
150
|
-
|
171
|
+
internal_exec_query(sql, name)
|
151
172
|
end
|
152
173
|
|
153
|
-
def explain(arel, binds = []) # :nodoc:
|
174
|
+
def explain(arel, binds = [], options = []) # :nodoc:
|
154
175
|
raise NotImplementedError
|
155
176
|
end
|
156
177
|
|
@@ -162,9 +183,15 @@ module ActiveRecord
|
|
162
183
|
#
|
163
184
|
# If the next id was calculated in advance (as in Oracle), it should be
|
164
185
|
# passed in as +id_value+.
|
165
|
-
|
186
|
+
# Some adapters support the `returning` keyword argument which allows defining the return value of the method:
|
187
|
+
# `nil` is the default value and maintains default behavior. If an array of column names is passed -
|
188
|
+
# an array of is returned from the method representing values of the specified columns from the inserted row.
|
189
|
+
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil)
|
166
190
|
sql, binds = to_sql_and_binds(arel, binds)
|
167
|
-
value = exec_insert(sql, name, binds, pk, sequence_name)
|
191
|
+
value = exec_insert(sql, name, binds, pk, sequence_name, returning: returning)
|
192
|
+
|
193
|
+
return returning_column_values(value) unless returning.nil?
|
194
|
+
|
168
195
|
id_value || last_inserted_id(value)
|
169
196
|
end
|
170
197
|
alias create insert
|
@@ -187,7 +214,7 @@ module ActiveRecord
|
|
187
214
|
end
|
188
215
|
|
189
216
|
def truncate_tables(*table_names) # :nodoc:
|
190
|
-
table_names -= [schema_migration.table_name,
|
217
|
+
table_names -= [schema_migration.table_name, internal_metadata.table_name]
|
191
218
|
|
192
219
|
return if table_names.empty?
|
193
220
|
|
@@ -304,8 +331,9 @@ module ActiveRecord
|
|
304
331
|
# * You are joining an existing open transaction
|
305
332
|
# * You are creating a nested (savepoint) transaction
|
306
333
|
#
|
307
|
-
# The mysql2 and postgresql adapters support setting the transaction
|
334
|
+
# The mysql2, trilogy, and postgresql adapters support setting the transaction
|
308
335
|
# isolation level.
|
336
|
+
# :args: (requires_new: nil, isolation: nil, &block)
|
309
337
|
def transaction(requires_new: nil, isolation: nil, joinable: true, &block)
|
310
338
|
if !requires_new && current_transaction.joinable?
|
311
339
|
if isolation
|
@@ -323,7 +351,8 @@ module ActiveRecord
|
|
323
351
|
|
324
352
|
delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction,
|
325
353
|
:commit_transaction, :rollback_transaction, :materialize_transactions,
|
326
|
-
:disable_lazy_transactions!, :enable_lazy_transactions!,
|
354
|
+
:disable_lazy_transactions!, :enable_lazy_transactions!, :dirty_current_transaction,
|
355
|
+
to: :transaction_manager
|
327
356
|
|
328
357
|
def mark_transaction_written_if_write(sql) # :nodoc:
|
329
358
|
transaction = current_transaction
|
@@ -336,8 +365,24 @@ module ActiveRecord
|
|
336
365
|
current_transaction.open?
|
337
366
|
end
|
338
367
|
|
339
|
-
def reset_transaction # :nodoc:
|
368
|
+
def reset_transaction(restore: false) # :nodoc:
|
369
|
+
# Store the existing transaction state to the side
|
370
|
+
old_state = @transaction_manager if restore && @transaction_manager&.restorable?
|
371
|
+
|
340
372
|
@transaction_manager = ConnectionAdapters::TransactionManager.new(self)
|
373
|
+
|
374
|
+
if block_given?
|
375
|
+
# Reconfigure the connection without any transaction state in the way
|
376
|
+
result = yield
|
377
|
+
|
378
|
+
# Now the connection's fully established, we can swap back
|
379
|
+
if old_state
|
380
|
+
@transaction_manager = old_state
|
381
|
+
@transaction_manager.restore_transactions
|
382
|
+
end
|
383
|
+
|
384
|
+
result
|
385
|
+
end
|
341
386
|
end
|
342
387
|
|
343
388
|
# Register a record with the current transaction so that its after_commit and after_rollback callbacks
|
@@ -372,10 +417,18 @@ module ActiveRecord
|
|
372
417
|
# done if the transaction block raises an exception or returns false.
|
373
418
|
def rollback_db_transaction
|
374
419
|
exec_rollback_db_transaction
|
420
|
+
rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::ConnectionFailed
|
421
|
+
# Connection's gone; that counts as a rollback
|
375
422
|
end
|
376
423
|
|
377
424
|
def exec_rollback_db_transaction() end # :nodoc:
|
378
425
|
|
426
|
+
def restart_db_transaction
|
427
|
+
exec_restart_db_transaction
|
428
|
+
end
|
429
|
+
|
430
|
+
def exec_restart_db_transaction() end # :nodoc:
|
431
|
+
|
379
432
|
def rollback_to_savepoint(name = nil)
|
380
433
|
exec_rollback_to_savepoint(name)
|
381
434
|
end
|
@@ -393,7 +446,7 @@ module ActiveRecord
|
|
393
446
|
# something beyond a simple insert (e.g. Oracle).
|
394
447
|
# Most of adapters should implement +insert_fixtures_set+ that leverages bulk SQL insert.
|
395
448
|
# We keep this method to provide fallback
|
396
|
-
# for databases like
|
449
|
+
# for databases like SQLite that do not support bulk inserts.
|
397
450
|
def insert_fixture(fixture, table_name)
|
398
451
|
execute(build_fixture_sql(Array.wrap(fixture), table_name), "Fixture Insert")
|
399
452
|
end
|
@@ -454,13 +507,30 @@ module ActiveRecord
|
|
454
507
|
HIGH_PRECISION_CURRENT_TIMESTAMP
|
455
508
|
end
|
456
509
|
|
510
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
|
511
|
+
raise NotImplementedError
|
512
|
+
end
|
513
|
+
|
457
514
|
private
|
515
|
+
def internal_execute(sql, name = "SCHEMA", allow_retry: false, materialize_transactions: true)
|
516
|
+
sql = transform_query(sql)
|
517
|
+
check_if_write_query(sql)
|
518
|
+
|
519
|
+
mark_transaction_written_if_write(sql)
|
520
|
+
|
521
|
+
raw_execute(sql, name, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
|
522
|
+
end
|
523
|
+
|
458
524
|
def execute_batch(statements, name = nil)
|
459
525
|
statements.each do |statement|
|
460
|
-
|
526
|
+
internal_execute(statement, name)
|
461
527
|
end
|
462
528
|
end
|
463
529
|
|
530
|
+
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
531
|
+
raise NotImplementedError
|
532
|
+
end
|
533
|
+
|
464
534
|
DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
|
465
535
|
private_constant :DEFAULT_INSERT_VALUE
|
466
536
|
|
@@ -557,10 +627,28 @@ module ActiveRecord
|
|
557
627
|
return future_result
|
558
628
|
end
|
559
629
|
|
560
|
-
|
630
|
+
result = internal_exec_query(sql, name, binds, prepare: prepare)
|
631
|
+
if async
|
632
|
+
FutureResult.wrap(result)
|
633
|
+
else
|
634
|
+
result
|
635
|
+
end
|
561
636
|
end
|
562
637
|
|
563
|
-
def sql_for_insert(sql, pk, binds)
|
638
|
+
def sql_for_insert(sql, pk, binds, returning) # :nodoc:
|
639
|
+
if supports_insert_returning?
|
640
|
+
if pk.nil?
|
641
|
+
# Extract the table from the insert sql. Yuck.
|
642
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
643
|
+
pk = primary_key(table_ref) if table_ref
|
644
|
+
end
|
645
|
+
|
646
|
+
returning_columns = returning || Array(pk)
|
647
|
+
|
648
|
+
returning_columns_statement = returning_columns.map { |c| quote_column_name(c) }.join(", ")
|
649
|
+
sql = "#{sql} RETURNING #{returning_columns_statement}" if returning_columns.any?
|
650
|
+
end
|
651
|
+
|
564
652
|
[sql, binds]
|
565
653
|
end
|
566
654
|
|
@@ -568,6 +656,10 @@ module ActiveRecord
|
|
568
656
|
single_value_from_rows(result.rows)
|
569
657
|
end
|
570
658
|
|
659
|
+
def returning_column_values(result)
|
660
|
+
[last_inserted_id(result)]
|
661
|
+
end
|
662
|
+
|
571
663
|
def single_value_from_rows(rows)
|
572
664
|
row = rows.first
|
573
665
|
row && row.first
|
@@ -580,6 +672,12 @@ module ActiveRecord
|
|
580
672
|
relation
|
581
673
|
end
|
582
674
|
end
|
675
|
+
|
676
|
+
def extract_table_ref_from_insert_sql(sql)
|
677
|
+
if sql =~ /into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im
|
678
|
+
$1.delete('"').strip
|
679
|
+
end
|
680
|
+
end
|
583
681
|
end
|
584
682
|
end
|
585
683
|
end
|
@@ -5,10 +5,13 @@ require "concurrent/map"
|
|
5
5
|
module ActiveRecord
|
6
6
|
module ConnectionAdapters # :nodoc:
|
7
7
|
module QueryCache
|
8
|
+
DEFAULT_SIZE = 100 # :nodoc:
|
9
|
+
|
8
10
|
class << self
|
9
11
|
def included(base) # :nodoc:
|
10
|
-
dirties_query_cache base, :create, :insert, :update, :delete, :truncate,
|
11
|
-
:rollback_to_savepoint, :rollback_db_transaction, :
|
12
|
+
dirties_query_cache base, :exec_query, :execute, :create, :insert, :update, :delete, :truncate,
|
13
|
+
:truncate_tables, :rollback_to_savepoint, :rollback_db_transaction, :restart_db_transaction,
|
14
|
+
:exec_insert_all
|
12
15
|
|
13
16
|
base.set_callback :checkout, :after, :configure_query_cache!
|
14
17
|
base.set_callback :checkin, :after, :disable_query_cache!
|
@@ -17,7 +20,7 @@ module ActiveRecord
|
|
17
20
|
def dirties_query_cache(base, *method_names)
|
18
21
|
method_names.each do |method_name|
|
19
22
|
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
|
20
|
-
def #{method_name}(
|
23
|
+
def #{method_name}(...)
|
21
24
|
ActiveRecord::Base.clear_query_caches_for_current_thread
|
22
25
|
super
|
23
26
|
end
|
@@ -51,8 +54,9 @@ module ActiveRecord
|
|
51
54
|
|
52
55
|
def initialize(*)
|
53
56
|
super
|
54
|
-
@query_cache =
|
57
|
+
@query_cache = {}
|
55
58
|
@query_cache_enabled = false
|
59
|
+
@query_cache_max_size = nil
|
56
60
|
end
|
57
61
|
|
58
62
|
# Enable the query cache within the block.
|
@@ -93,7 +97,7 @@ module ActiveRecord
|
|
93
97
|
end
|
94
98
|
end
|
95
99
|
|
96
|
-
def select_all(arel, name = nil, binds = [], preparable: nil, async: false)
|
100
|
+
def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :nodoc:
|
97
101
|
arel = arel_from_relation(arel)
|
98
102
|
|
99
103
|
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
|
@@ -102,7 +106,8 @@ module ActiveRecord
|
|
102
106
|
sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
|
103
107
|
|
104
108
|
if async
|
105
|
-
lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
|
109
|
+
result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
|
110
|
+
FutureResult.wrap(result)
|
106
111
|
else
|
107
112
|
cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async) }
|
108
113
|
end
|
@@ -113,31 +118,52 @@ module ActiveRecord
|
|
113
118
|
|
114
119
|
private
|
115
120
|
def lookup_sql_cache(sql, name, binds)
|
121
|
+
key = binds.empty? ? sql : [sql, binds]
|
122
|
+
hit = false
|
123
|
+
result = nil
|
124
|
+
|
116
125
|
@lock.synchronize do
|
117
|
-
if @query_cache
|
118
|
-
|
119
|
-
|
120
|
-
cache_notification_info(sql, name, binds)
|
121
|
-
)
|
122
|
-
@query_cache[sql][binds]
|
126
|
+
if (result = @query_cache.delete(key))
|
127
|
+
hit = true
|
128
|
+
@query_cache[key] = result
|
123
129
|
end
|
124
130
|
end
|
131
|
+
|
132
|
+
if hit
|
133
|
+
ActiveSupport::Notifications.instrument(
|
134
|
+
"sql.active_record",
|
135
|
+
cache_notification_info(sql, name, binds)
|
136
|
+
)
|
137
|
+
|
138
|
+
result
|
139
|
+
end
|
125
140
|
end
|
126
141
|
|
127
142
|
def cache_sql(sql, name, binds)
|
143
|
+
key = binds.empty? ? sql : [sql, binds]
|
144
|
+
result = nil
|
145
|
+
hit = false
|
146
|
+
|
128
147
|
@lock.synchronize do
|
129
|
-
result =
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
@query_cache
|
136
|
-
else
|
137
|
-
@query_cache[sql][binds] = yield
|
148
|
+
if (result = @query_cache.delete(key))
|
149
|
+
hit = true
|
150
|
+
@query_cache[key] = result
|
151
|
+
else
|
152
|
+
result = @query_cache[key] = yield
|
153
|
+
if @query_cache_max_size && @query_cache.size > @query_cache_max_size
|
154
|
+
@query_cache.shift
|
138
155
|
end
|
139
|
-
|
156
|
+
end
|
140
157
|
end
|
158
|
+
|
159
|
+
if hit
|
160
|
+
ActiveSupport::Notifications.instrument(
|
161
|
+
"sql.active_record",
|
162
|
+
cache_notification_info(sql, name, binds)
|
163
|
+
)
|
164
|
+
end
|
165
|
+
|
166
|
+
result.dup
|
141
167
|
end
|
142
168
|
|
143
169
|
# Database adapters can override this method to
|
@@ -154,7 +180,20 @@ module ActiveRecord
|
|
154
180
|
end
|
155
181
|
|
156
182
|
def configure_query_cache!
|
157
|
-
|
183
|
+
case query_cache = pool.db_config.query_cache
|
184
|
+
when 0, false
|
185
|
+
return
|
186
|
+
when Integer
|
187
|
+
@query_cache_max_size = query_cache
|
188
|
+
when nil
|
189
|
+
@query_cache_max_size = DEFAULT_SIZE
|
190
|
+
else
|
191
|
+
@query_cache_max_size = nil # no limit
|
192
|
+
end
|
193
|
+
|
194
|
+
if pool.query_cache_enabled
|
195
|
+
enable_query_cache!
|
196
|
+
end
|
158
197
|
end
|
159
198
|
end
|
160
199
|
end
|
@@ -5,6 +5,7 @@ require "active_support/multibyte/chars"
|
|
5
5
|
|
6
6
|
module ActiveRecord
|
7
7
|
module ConnectionAdapters # :nodoc:
|
8
|
+
# = Active Record Connection Adapters \Quoting
|
8
9
|
module Quoting
|
9
10
|
# Quotes the column value to help prevent
|
10
11
|
# {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection].
|
@@ -17,11 +18,14 @@ module ActiveRecord
|
|
17
18
|
when nil then "NULL"
|
18
19
|
# BigDecimals need to be put in a non-normalized form and quoted.
|
19
20
|
when BigDecimal then value.to_s("F")
|
20
|
-
when Numeric
|
21
|
+
when Numeric then value.to_s
|
21
22
|
when Type::Binary::Data then quoted_binary(value)
|
22
23
|
when Type::Time::Value then "'#{quoted_time(value)}'"
|
23
24
|
when Date, Time then "'#{quoted_date(value)}'"
|
24
25
|
when Class then "'#{value}'"
|
26
|
+
when ActiveSupport::Duration
|
27
|
+
warn_quote_duration_deprecated
|
28
|
+
value.to_s
|
25
29
|
else raise TypeError, "can't quote #{value.class.name}"
|
26
30
|
end
|
27
31
|
end
|
@@ -47,8 +51,23 @@ module ActiveRecord
|
|
47
51
|
# Quote a value to be used as a bound parameter of unknown type. For example,
|
48
52
|
# MySQL might perform dangerous castings when comparing a string to a number,
|
49
53
|
# so this method will cast numbers to string.
|
54
|
+
#
|
55
|
+
# Deprecated: Consider `Arel.sql("... ? ...", value)` or
|
56
|
+
# +sanitize_sql+ instead.
|
50
57
|
def quote_bound_value(value)
|
51
|
-
|
58
|
+
ActiveRecord.deprecator.warn(<<~MSG.squish)
|
59
|
+
#quote_bound_value is deprecated and will be removed in Rails 7.2.
|
60
|
+
Consider Arel.sql(".. ? ..", value) or #sanitize_sql instead.
|
61
|
+
MSG
|
62
|
+
|
63
|
+
quote(cast_bound_value(value))
|
64
|
+
end
|
65
|
+
|
66
|
+
# Cast a value to be used as a bound parameter of unknown type. For example,
|
67
|
+
# MySQL might perform dangerous castings when comparing a string to a number,
|
68
|
+
# so this method will cast numbers to string.
|
69
|
+
def cast_bound_value(value) # :nodoc:
|
70
|
+
value
|
52
71
|
end
|
53
72
|
|
54
73
|
# If you are having to call this function, you are likely doing something
|
@@ -83,7 +102,7 @@ module ActiveRecord
|
|
83
102
|
# Override to return the quoted table name for assignment. Defaults to
|
84
103
|
# table quoting.
|
85
104
|
#
|
86
|
-
# This works for
|
105
|
+
# This works for MySQL where table.column can be used to
|
87
106
|
# resolve ambiguity.
|
88
107
|
#
|
89
108
|
# We override this in the sqlite3 and postgresql adapters to use only
|
@@ -121,7 +140,7 @@ module ActiveRecord
|
|
121
140
|
# if the value is a Time responding to usec.
|
122
141
|
def quoted_date(value)
|
123
142
|
if value.acts_like?(:time)
|
124
|
-
if
|
143
|
+
if default_timezone == :utc
|
125
144
|
value = value.getutc if !value.utc?
|
126
145
|
else
|
127
146
|
value = value.getlocal
|
@@ -176,7 +195,7 @@ module ActiveRecord
|
|
176
195
|
(
|
177
196
|
(?:
|
178
197
|
# table_name.column_name | function(one or no argument)
|
179
|
-
((?:\w+\.)?\w+
|
198
|
+
((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
|
180
199
|
)
|
181
200
|
(?:(?:\s+AS)?\s+\w+)?
|
182
201
|
)
|
@@ -200,7 +219,7 @@ module ActiveRecord
|
|
200
219
|
(
|
201
220
|
(?:
|
202
221
|
# table_name.column_name | function(one or no argument)
|
203
|
-
((?:\w+\.)?\w+
|
222
|
+
((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
|
204
223
|
)
|
205
224
|
(?:\s+ASC|\s+DESC)?
|
206
225
|
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
@@ -225,6 +244,22 @@ module ActiveRecord
|
|
225
244
|
def lookup_cast_type(sql_type)
|
226
245
|
type_map.lookup(sql_type)
|
227
246
|
end
|
247
|
+
|
248
|
+
def warn_quote_duration_deprecated
|
249
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
250
|
+
Using ActiveSupport::Duration as an interpolated bind parameter in a SQL
|
251
|
+
string template is deprecated. To avoid this warning, you should explicitly
|
252
|
+
convert the duration to a more specific database type. For example, if you
|
253
|
+
want to use a duration as an integer number of seconds:
|
254
|
+
```
|
255
|
+
Record.where("duration = ?", 1.hour.to_i)
|
256
|
+
```
|
257
|
+
If you want to use a duration as an ISO 8601 string:
|
258
|
+
```
|
259
|
+
Record.where("duration = ?", 1.hour.iso8601)
|
260
|
+
```
|
261
|
+
MSG
|
262
|
+
end
|
228
263
|
end
|
229
264
|
end
|
230
265
|
end
|
@@ -2,21 +2,22 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module ConnectionAdapters
|
5
|
+
# = Active Record Connection Adapters \Savepoints
|
5
6
|
module Savepoints
|
6
7
|
def current_savepoint_name
|
7
8
|
current_transaction.savepoint_name
|
8
9
|
end
|
9
10
|
|
10
11
|
def create_savepoint(name = current_savepoint_name)
|
11
|
-
|
12
|
+
internal_execute("SAVEPOINT #{name}", "TRANSACTION")
|
12
13
|
end
|
13
14
|
|
14
15
|
def exec_rollback_to_savepoint(name = current_savepoint_name)
|
15
|
-
|
16
|
+
internal_execute("ROLLBACK TO SAVEPOINT #{name}", "TRANSACTION")
|
16
17
|
end
|
17
18
|
|
18
19
|
def release_savepoint(name = current_savepoint_name)
|
19
|
-
|
20
|
+
internal_execute("RELEASE SAVEPOINT #{name}", "TRANSACTION")
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
@@ -14,8 +14,10 @@ module ActiveRecord
|
|
14
14
|
end
|
15
15
|
|
16
16
|
delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
|
17
|
-
:options_include_default?, :supports_indexes_in_create?, :
|
17
|
+
:options_include_default?, :supports_indexes_in_create?, :use_foreign_keys?,
|
18
18
|
:quoted_columns_for_index, :supports_partial_index?, :supports_check_constraints?,
|
19
|
+
:supports_index_include?, :supports_exclusion_constraints?, :supports_unique_constraints?,
|
20
|
+
:supports_nulls_not_distinct?,
|
19
21
|
to: :@conn, private: true
|
20
22
|
|
21
23
|
private
|
@@ -51,7 +53,7 @@ module ActiveRecord
|
|
51
53
|
statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
|
52
54
|
end
|
53
55
|
|
54
|
-
if
|
56
|
+
if use_foreign_keys?
|
55
57
|
statements.concat(o.foreign_keys.map { |fk| accept fk })
|
56
58
|
end
|
57
59
|
|
@@ -59,6 +61,14 @@ module ActiveRecord
|
|
59
61
|
statements.concat(o.check_constraints.map { |chk| accept chk })
|
60
62
|
end
|
61
63
|
|
64
|
+
if supports_exclusion_constraints?
|
65
|
+
statements.concat(o.exclusion_constraints.map { |exc| accept exc })
|
66
|
+
end
|
67
|
+
|
68
|
+
if supports_unique_constraints?
|
69
|
+
statements.concat(o.unique_constraints.map { |exc| accept exc })
|
70
|
+
end
|
71
|
+
|
62
72
|
create_sql << "(#{statements.join(', ')})" if statements.present?
|
63
73
|
add_table_options!(create_sql, o)
|
64
74
|
create_sql << " AS #{to_sql(o.as)}" if o.as
|
@@ -70,10 +80,12 @@ module ActiveRecord
|
|
70
80
|
end
|
71
81
|
|
72
82
|
def visit_ForeignKeyDefinition(o)
|
83
|
+
quoted_columns = Array(o.column).map { |c| quote_column_name(c) }
|
84
|
+
quoted_primary_keys = Array(o.primary_key).map { |c| quote_column_name(c) }
|
73
85
|
sql = +<<~SQL
|
74
86
|
CONSTRAINT #{quote_column_name(o.name)}
|
75
|
-
FOREIGN KEY (#{
|
76
|
-
REFERENCES #{quote_table_name(o.to_table)} (#{
|
87
|
+
FOREIGN KEY (#{quoted_columns.join(", ")})
|
88
|
+
REFERENCES #{quote_table_name(o.to_table)} (#{quoted_primary_keys.join(", ")})
|
77
89
|
SQL
|
78
90
|
sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete
|
79
91
|
sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update
|
@@ -100,6 +112,8 @@ module ActiveRecord
|
|
100
112
|
sql << "#{quote_column_name(index.name)} ON #{quote_table_name(index.table)}"
|
101
113
|
sql << "USING #{index.using}" if supports_index_using? && index.using
|
102
114
|
sql << "(#{quoted_columns(index)})"
|
115
|
+
sql << "INCLUDE (#{quoted_include_columns(index.include)})" if supports_index_include? && index.include
|
116
|
+
sql << "NULLS NOT DISTINCT" if supports_nulls_not_distinct? && index.nulls_not_distinct
|
103
117
|
sql << "WHERE #{index.where}" if supports_partial_index? && index.where
|
104
118
|
|
105
119
|
sql.join(" ")
|