activerecord 7.1.3.4 → 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 +652 -2032
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +15 -8
- data/lib/active_record/associations/belongs_to_association.rb +18 -11
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +11 -5
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +3 -3
- data/lib/active_record/associations/has_many_through_association.rb +7 -1
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -12
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +62 -289
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +2 -2
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +4 -16
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +89 -58
- data/lib/active_record/attributes.rb +61 -47
- data/lib/active_record/autosave_association.rb +17 -31
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -58
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +190 -75
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +23 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +73 -19
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- 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/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +18 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +127 -77
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +93 -40
- data/lib/active_record/counter_cache.rb +23 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +19 -4
- data/lib/active_record/database_configurations/hash_config.rb +44 -36
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +30 -6
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +3 -3
- data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +8 -4
- data/lib/active_record/encryption.rb +2 -0
- data/lib/active_record/enum.rb +19 -2
- data/lib/active_record/errors.rb +46 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +17 -4
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +4 -1
- data/lib/active_record/message_pack.rb +2 -2
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +11 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +85 -76
- data/lib/active_record/model_schema.rb +38 -70
- data/lib/active_record/nested_attributes.rb +24 -5
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +32 -354
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/query_logs_formatter.rb +1 -1
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +50 -68
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +42 -45
- data/lib/active_record/reflection.rb +106 -38
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +14 -8
- data/lib/active_record/relation/calculations.rb +96 -63
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +245 -65
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +500 -66
- data/lib/active_record/result.rb +32 -45
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/scoping/named.rb +1 -0
- data/lib/active_record/signed_id.rb +20 -1
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +98 -48
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +87 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +5 -3
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +70 -14
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +15 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +150 -41
- 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/nodes/binary.rb +0 -6
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/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 +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +31 -17
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +21 -15
@@ -5,6 +5,18 @@ module ActiveRecord
|
|
5
5
|
module SQLite3
|
6
6
|
class SchemaCreation < SchemaCreation # :nodoc:
|
7
7
|
private
|
8
|
+
def visit_AddForeignKey(o)
|
9
|
+
super.dup.tap do |sql|
|
10
|
+
sql << " DEFERRABLE INITIALLY #{o.options[:deferrable].to_s.upcase}" if o.deferrable
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_ForeignKeyDefinition(o)
|
15
|
+
super.dup.tap do |sql|
|
16
|
+
sql << " DEFERRABLE INITIALLY #{o.deferrable.to_s.upcase}" if o.deferrable
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
8
20
|
def supports_index_using?
|
9
21
|
false
|
10
22
|
end
|
@@ -13,6 +25,16 @@ module ActiveRecord
|
|
13
25
|
if options[:collation]
|
14
26
|
sql << " COLLATE \"#{options[:collation]}\""
|
15
27
|
end
|
28
|
+
|
29
|
+
if as = options[:as]
|
30
|
+
sql << " GENERATED ALWAYS AS (#{as})"
|
31
|
+
|
32
|
+
if options[:stored]
|
33
|
+
sql << " STORED"
|
34
|
+
else
|
35
|
+
sql << " VIRTUAL"
|
36
|
+
end
|
37
|
+
end
|
16
38
|
super
|
17
39
|
end
|
18
40
|
end
|
@@ -16,10 +16,23 @@ module ActiveRecord
|
|
16
16
|
end
|
17
17
|
alias :belongs_to :references
|
18
18
|
|
19
|
+
def new_column_definition(name, type, **options) # :nodoc:
|
20
|
+
case type
|
21
|
+
when :virtual
|
22
|
+
type = options[:type]
|
23
|
+
end
|
24
|
+
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
19
28
|
private
|
20
29
|
def integer_like_primary_key_type(type, options)
|
21
30
|
:primary_key
|
22
31
|
end
|
32
|
+
|
33
|
+
def valid_column_definition_options
|
34
|
+
super + [:as, :type, :stored]
|
35
|
+
end
|
23
36
|
end
|
24
37
|
end
|
25
38
|
end
|
@@ -12,6 +12,22 @@ module ActiveRecord
|
|
12
12
|
def explicit_primary_key_default?(column)
|
13
13
|
column.bigint?
|
14
14
|
end
|
15
|
+
|
16
|
+
def prepare_column_options(column)
|
17
|
+
spec = super
|
18
|
+
|
19
|
+
if @connection.supports_virtual_columns? && column.virtual?
|
20
|
+
spec[:as] = extract_expression_for_virtual_column(column)
|
21
|
+
spec[:stored] = column.virtual_stored?
|
22
|
+
spec = { type: schema_type(column).inspect }.merge!(spec)
|
23
|
+
end
|
24
|
+
|
25
|
+
spec
|
26
|
+
end
|
27
|
+
|
28
|
+
def extract_expression_for_virtual_column(column)
|
29
|
+
column.default_function.inspect
|
30
|
+
end
|
15
31
|
end
|
16
32
|
end
|
17
33
|
end
|
@@ -53,6 +53,8 @@ module ActiveRecord
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def add_foreign_key(from_table, to_table, **options)
|
56
|
+
assert_valid_deferrable(options[:deferrable])
|
57
|
+
|
56
58
|
alter_table(from_table) do |definition|
|
57
59
|
to_table = strip_table_name_prefix_and_suffix(to_table)
|
58
60
|
definition.foreign_key(to_table, **options)
|
@@ -137,7 +139,14 @@ module ActiveRecord
|
|
137
139
|
|
138
140
|
type_metadata = fetch_type_metadata(field["type"])
|
139
141
|
default_value = extract_value_from_default(default)
|
140
|
-
|
142
|
+
generated_type = extract_generated_type(field)
|
143
|
+
|
144
|
+
if generated_type.present?
|
145
|
+
default_function = default
|
146
|
+
else
|
147
|
+
default_function = extract_default_function(default_value, default)
|
148
|
+
end
|
149
|
+
|
141
150
|
rowid = is_column_the_rowid?(field, definitions)
|
142
151
|
|
143
152
|
Column.new(
|
@@ -148,7 +157,8 @@ module ActiveRecord
|
|
148
157
|
default_function,
|
149
158
|
collation: field["collation"],
|
150
159
|
auto_increment: field["auto_increment"],
|
151
|
-
rowid: rowid
|
160
|
+
rowid: rowid,
|
161
|
+
generated_type: generated_type
|
152
162
|
)
|
153
163
|
end
|
154
164
|
|
@@ -185,6 +195,19 @@ module ActiveRecord
|
|
185
195
|
scope[:type] = type if type
|
186
196
|
scope
|
187
197
|
end
|
198
|
+
|
199
|
+
def assert_valid_deferrable(deferrable)
|
200
|
+
return if !deferrable || %i(immediate deferred).include?(deferrable)
|
201
|
+
|
202
|
+
raise ArgumentError, "deferrable must be `:immediate` or `:deferred`, got: `#{deferrable.inspect}`"
|
203
|
+
end
|
204
|
+
|
205
|
+
def extract_generated_type(field)
|
206
|
+
case field["hidden"]
|
207
|
+
when 2 then :virtual
|
208
|
+
when 3 then :stored
|
209
|
+
end
|
210
|
+
end
|
188
211
|
end
|
189
212
|
end
|
190
213
|
end
|
@@ -11,20 +11,10 @@ require "active_record/connection_adapters/sqlite3/schema_definitions"
|
|
11
11
|
require "active_record/connection_adapters/sqlite3/schema_dumper"
|
12
12
|
require "active_record/connection_adapters/sqlite3/schema_statements"
|
13
13
|
|
14
|
-
gem "sqlite3", "
|
14
|
+
gem "sqlite3", ">= 1.4"
|
15
15
|
require "sqlite3"
|
16
16
|
|
17
17
|
module ActiveRecord
|
18
|
-
module ConnectionHandling # :nodoc:
|
19
|
-
def sqlite3_adapter_class
|
20
|
-
ConnectionAdapters::SQLite3Adapter
|
21
|
-
end
|
22
|
-
|
23
|
-
def sqlite3_connection(config)
|
24
|
-
sqlite3_adapter_class.new(config)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
18
|
module ConnectionAdapters # :nodoc:
|
29
19
|
# = Active Record SQLite3 Adapter
|
30
20
|
#
|
@@ -88,6 +78,15 @@ module ActiveRecord
|
|
88
78
|
json: { name: "json" },
|
89
79
|
}
|
90
80
|
|
81
|
+
DEFAULT_PRAGMAS = {
|
82
|
+
"foreign_keys" => true,
|
83
|
+
"journal_mode" => :wal,
|
84
|
+
"synchronous" => :normal,
|
85
|
+
"mmap_size" => 134217728, # 128 megabytes
|
86
|
+
"journal_size_limit" => 67108864, # 64 megabytes
|
87
|
+
"cache_size" => 2000
|
88
|
+
}
|
89
|
+
|
91
90
|
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
92
91
|
alias reset clear
|
93
92
|
|
@@ -113,13 +112,9 @@ module ActiveRecord
|
|
113
112
|
dirname = File.dirname(@config[:database])
|
114
113
|
unless File.directory?(dirname)
|
115
114
|
begin
|
116
|
-
|
117
|
-
rescue
|
118
|
-
|
119
|
-
raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
|
120
|
-
else
|
121
|
-
raise
|
122
|
-
end
|
115
|
+
FileUtils.mkdir_p(dirname)
|
116
|
+
rescue SystemCallError
|
117
|
+
raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
|
123
118
|
end
|
124
119
|
end
|
125
120
|
end
|
@@ -196,14 +191,16 @@ module ActiveRecord
|
|
196
191
|
!@memory_database
|
197
192
|
end
|
198
193
|
|
199
|
-
def
|
200
|
-
|
194
|
+
def supports_virtual_columns?
|
195
|
+
database_version >= "3.31.0"
|
201
196
|
end
|
202
197
|
|
203
|
-
def
|
204
|
-
|
198
|
+
def connected?
|
199
|
+
!(@raw_connection.nil? || @raw_connection.closed?)
|
205
200
|
end
|
206
201
|
|
202
|
+
alias_method :active?, :connected?
|
203
|
+
|
207
204
|
alias :reset! :reconnect!
|
208
205
|
|
209
206
|
# Disconnects from the database if already connected. Otherwise, this
|
@@ -236,6 +233,10 @@ module ActiveRecord
|
|
236
233
|
true
|
237
234
|
end
|
238
235
|
|
236
|
+
def supports_deferrable_constraints?
|
237
|
+
true
|
238
|
+
end
|
239
|
+
|
239
240
|
# REFERENTIAL INTEGRITY ====================================
|
240
241
|
|
241
242
|
def disable_referential_integrity # :nodoc:
|
@@ -258,7 +259,7 @@ module ActiveRecord
|
|
258
259
|
|
259
260
|
unless result.blank?
|
260
261
|
tables = result.map { |row| row["table"] }
|
261
|
-
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql)
|
262
|
+
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql, connection_pool: @pool)
|
262
263
|
end
|
263
264
|
end
|
264
265
|
|
@@ -286,10 +287,11 @@ module ActiveRecord
|
|
286
287
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
287
288
|
schema_cache.clear_data_source_cache!(new_name.to_s)
|
288
289
|
exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
289
|
-
rename_table_indexes(table_name, new_name)
|
290
|
+
rename_table_indexes(table_name, new_name, **options)
|
290
291
|
end
|
291
292
|
|
292
293
|
def add_column(table_name, column_name, type, **options) # :nodoc:
|
294
|
+
type = type.to_sym
|
293
295
|
if invalid_alter_table_type?(type, options)
|
294
296
|
alter_table(table_name) do |definition|
|
295
297
|
definition.column(column_name, type, **options)
|
@@ -365,15 +367,31 @@ module ActiveRecord
|
|
365
367
|
end
|
366
368
|
alias :add_belongs_to :add_reference
|
367
369
|
|
370
|
+
FK_REGEX = /.*FOREIGN KEY\s+\("(\w+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
|
371
|
+
DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
|
368
372
|
def foreign_keys(table_name)
|
369
373
|
# SQLite returns 1 row for each column of composite foreign keys.
|
370
374
|
fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
|
375
|
+
# Deferred or immediate foreign keys can only be seen in the CREATE TABLE sql
|
376
|
+
fk_defs = table_structure_sql(table_name)
|
377
|
+
.select do |column_string|
|
378
|
+
column_string.start_with?("CONSTRAINT") &&
|
379
|
+
column_string.include?("FOREIGN KEY")
|
380
|
+
end
|
381
|
+
.to_h do |fk_string|
|
382
|
+
_, from, table, to = fk_string.match(FK_REGEX).to_a
|
383
|
+
_, mode = fk_string.match(DEFERRABLE_REGEX).to_a
|
384
|
+
deferred = mode&.downcase&.to_sym || false
|
385
|
+
[[table, from, to], deferred]
|
386
|
+
end
|
387
|
+
|
371
388
|
grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
|
372
389
|
grouped_fk.map do |group|
|
373
390
|
row = group.first
|
374
391
|
options = {
|
375
392
|
on_delete: extract_foreign_key_action(row["on_delete"]),
|
376
|
-
on_update: extract_foreign_key_action(row["on_update"])
|
393
|
+
on_update: extract_foreign_key_action(row["on_update"]),
|
394
|
+
deferrable: fk_defs[[row["table"], row["from"], row["to"]]]
|
377
395
|
}
|
378
396
|
|
379
397
|
if group.one?
|
@@ -454,8 +472,8 @@ module ActiveRecord
|
|
454
472
|
end
|
455
473
|
|
456
474
|
def table_structure(table_name)
|
457
|
-
structure =
|
458
|
-
raise
|
475
|
+
structure = table_info(table_name)
|
476
|
+
raise ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'", connection_pool: @pool) if structure.empty?
|
459
477
|
table_structure_with_collation(table_name, structure)
|
460
478
|
end
|
461
479
|
alias column_definitions table_structure
|
@@ -494,8 +512,9 @@ module ActiveRecord
|
|
494
512
|
# See: https://www.sqlite.org/lang_altertable.html
|
495
513
|
# SQLite has an additional restriction on the ALTER TABLE statement
|
496
514
|
def invalid_alter_table_type?(type, options)
|
497
|
-
type
|
498
|
-
options[:null] == false && options[:default].nil?
|
515
|
+
type == :primary_key || options[:primary_key] ||
|
516
|
+
options[:null] == false && options[:default].nil? ||
|
517
|
+
(type == :virtual && options[:stored])
|
499
518
|
end
|
500
519
|
|
501
520
|
def alter_table(
|
@@ -551,12 +570,6 @@ module ActiveRecord
|
|
551
570
|
options[:rename][column.name.to_sym] ||
|
552
571
|
column.name) : column.name
|
553
572
|
|
554
|
-
if column.has_default?
|
555
|
-
type = lookup_cast_type_from_column(column)
|
556
|
-
default = type.deserialize(column.default)
|
557
|
-
default = -> { column.default_function } if default.nil?
|
558
|
-
end
|
559
|
-
|
560
573
|
column_options = {
|
561
574
|
limit: column.limit,
|
562
575
|
precision: column.precision,
|
@@ -566,19 +579,31 @@ module ActiveRecord
|
|
566
579
|
primary_key: column_name == from_primary_key
|
567
580
|
}
|
568
581
|
|
569
|
-
|
570
|
-
column_options[:
|
582
|
+
if column.virtual?
|
583
|
+
column_options[:as] = column.default_function
|
584
|
+
column_options[:stored] = column.virtual_stored?
|
585
|
+
column_options[:type] = column.type
|
586
|
+
elsif column.has_default?
|
587
|
+
type = lookup_cast_type_from_column(column)
|
588
|
+
default = type.deserialize(column.default)
|
589
|
+
default = -> { column.default_function } if default.nil?
|
590
|
+
|
591
|
+
unless column.auto_increment?
|
592
|
+
column_options[:default] = default
|
593
|
+
end
|
571
594
|
end
|
572
595
|
|
573
|
-
column_type = column.bigint? ? :bigint : column.type
|
596
|
+
column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type)
|
574
597
|
@definition.column(column_name, column_type, **column_options)
|
575
598
|
end
|
576
599
|
|
577
600
|
yield @definition if block_given?
|
578
601
|
end
|
579
602
|
copy_table_indexes(from, to, options[:rename] || {})
|
603
|
+
|
604
|
+
columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name)
|
580
605
|
copy_table_contents(from, to,
|
581
|
-
|
606
|
+
columns_to_copy,
|
582
607
|
options[:rename] || {})
|
583
608
|
end
|
584
609
|
|
@@ -643,32 +668,22 @@ module ActiveRecord
|
|
643
668
|
|
644
669
|
COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
|
645
670
|
PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i
|
671
|
+
GENERATED_ALWAYS_AS_REGEX = /.*"(\w+)".+GENERATED ALWAYS AS \((.+)\) (?:STORED|VIRTUAL)/i
|
646
672
|
|
647
673
|
def table_structure_with_collation(table_name, basic_structure)
|
648
674
|
collation_hash = {}
|
649
675
|
auto_increments = {}
|
650
|
-
|
651
|
-
SELECT sql FROM
|
652
|
-
(SELECT * FROM sqlite_master UNION ALL
|
653
|
-
SELECT * FROM sqlite_temp_master)
|
654
|
-
WHERE type = 'table' AND name = #{quote(table_name)}
|
655
|
-
SQL
|
676
|
+
generated_columns = {}
|
656
677
|
|
657
|
-
|
658
|
-
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
659
|
-
# "password_digest" varchar COLLATE "NOCASE");
|
660
|
-
result = query_value(sql, "SCHEMA")
|
678
|
+
column_strings = table_structure_sql(table_name, basic_structure.map { |column| column["name"] })
|
661
679
|
|
662
|
-
if
|
663
|
-
|
664
|
-
# columns separated with comma(,).
|
665
|
-
columns_string = result.split("(", 2).last
|
666
|
-
|
667
|
-
columns_string.split(",").each do |column_string|
|
680
|
+
if column_strings.any?
|
681
|
+
column_strings.each do |column_string|
|
668
682
|
# This regex will match the column name and collation type and will save
|
669
683
|
# the value in $1 and $2 respectively.
|
670
684
|
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
671
685
|
auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
|
686
|
+
generated_columns[$1] = $2 if GENERATED_ALWAYS_AS_REGEX =~ column_string
|
672
687
|
end
|
673
688
|
|
674
689
|
basic_structure.map do |column|
|
@@ -682,6 +697,10 @@ module ActiveRecord
|
|
682
697
|
column["auto_increment"] = true
|
683
698
|
end
|
684
699
|
|
700
|
+
if generated_columns.has_key?(column_name)
|
701
|
+
column["dflt_value"] = generated_columns[column_name]
|
702
|
+
end
|
703
|
+
|
685
704
|
column
|
686
705
|
end
|
687
706
|
else
|
@@ -689,6 +708,50 @@ module ActiveRecord
|
|
689
708
|
end
|
690
709
|
end
|
691
710
|
|
711
|
+
UNQUOTED_OPEN_PARENS_REGEX = /\((?![^'"]*['"][^'"]*$)/
|
712
|
+
FINAL_CLOSE_PARENS_REGEX = /\);*\z/
|
713
|
+
|
714
|
+
def table_structure_sql(table_name, column_names = nil)
|
715
|
+
unless column_names
|
716
|
+
column_info = table_info(table_name)
|
717
|
+
column_names = column_info.map { |column| column["name"] }
|
718
|
+
end
|
719
|
+
|
720
|
+
sql = <<~SQL
|
721
|
+
SELECT sql FROM
|
722
|
+
(SELECT * FROM sqlite_master UNION ALL
|
723
|
+
SELECT * FROM sqlite_temp_master)
|
724
|
+
WHERE type = 'table' AND name = #{quote(table_name)}
|
725
|
+
SQL
|
726
|
+
|
727
|
+
# Result will have following sample string
|
728
|
+
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
729
|
+
# "password_digest" varchar COLLATE "NOCASE",
|
730
|
+
# "o_id" integer,
|
731
|
+
# CONSTRAINT "fk_rails_78146ddd2e" FOREIGN KEY ("o_id") REFERENCES "os" ("id"));
|
732
|
+
result = query_value(sql, "SCHEMA")
|
733
|
+
|
734
|
+
return [] unless result
|
735
|
+
|
736
|
+
# Splitting with left parentheses and discarding the first part will return all
|
737
|
+
# columns separated with comma(,).
|
738
|
+
result.partition(UNQUOTED_OPEN_PARENS_REGEX)
|
739
|
+
.last
|
740
|
+
.sub(FINAL_CLOSE_PARENS_REGEX, "")
|
741
|
+
# column definitions can have a comma in them, so split on commas followed
|
742
|
+
# by a space and a column name in quotes or followed by the keyword CONSTRAINT
|
743
|
+
.split(/,(?=\s(?:CONSTRAINT|"(?:#{Regexp.union(column_names).source})"))/i)
|
744
|
+
.map(&:strip)
|
745
|
+
end
|
746
|
+
|
747
|
+
def table_info(table_name)
|
748
|
+
if supports_virtual_columns?
|
749
|
+
internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
|
750
|
+
else
|
751
|
+
internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
752
|
+
end
|
753
|
+
end
|
754
|
+
|
692
755
|
def arel_visitor
|
693
756
|
Arel::Visitors::SQLite.new(self)
|
694
757
|
end
|
@@ -723,29 +786,16 @@ module ActiveRecord
|
|
723
786
|
end
|
724
787
|
end
|
725
788
|
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
# 2 = "FULL" (sync on every write), 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
|
736
|
-
# https://www.sqlite.org/pragma.html#pragma_synchronous
|
737
|
-
raw_execute("PRAGMA synchronous = NORMAL", "SCHEMA")
|
738
|
-
# Set the global memory map so all processes can share some data
|
739
|
-
# https://www.sqlite.org/pragma.html#pragma_mmap_size
|
740
|
-
# https://www.sqlite.org/mmap.html
|
741
|
-
raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA")
|
789
|
+
super
|
790
|
+
|
791
|
+
pragmas = @config.fetch(:pragmas, {}).stringify_keys
|
792
|
+
DEFAULT_PRAGMAS.merge(pragmas).each do |pragma, value|
|
793
|
+
if ::SQLite3::Pragmas.method_defined?("#{pragma}=")
|
794
|
+
@raw_connection.public_send("#{pragma}=", value)
|
795
|
+
else
|
796
|
+
warn "Unknown SQLite pragma: #{pragma}"
|
797
|
+
end
|
742
798
|
end
|
743
|
-
# Impose a limit on the WAL file to prevent unlimited growth
|
744
|
-
# https://www.sqlite.org/pragma.html#pragma_journal_size_limit
|
745
|
-
raw_execute("PRAGMA journal_size_limit = #{64.megabytes}", "SCHEMA")
|
746
|
-
# Set the local connection cache to 2000 pages
|
747
|
-
# https://www.sqlite.org/pragma.html#pragma_cache_size
|
748
|
-
raw_execute("PRAGMA cache_size = 2000", "SCHEMA")
|
749
799
|
end
|
750
800
|
end
|
751
801
|
ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
|
@@ -4,20 +4,12 @@ module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
5
5
|
module Trilogy
|
6
6
|
module DatabaseStatements
|
7
|
-
def
|
8
|
-
result = super
|
9
|
-
with_raw_connection do |conn|
|
10
|
-
conn.next_result while conn.more_results_exist?
|
11
|
-
end
|
12
|
-
result
|
13
|
-
end
|
14
|
-
|
15
|
-
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
|
7
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
|
16
8
|
sql = transform_query(sql)
|
17
9
|
check_if_write_query(sql)
|
18
10
|
mark_transaction_written_if_write(sql)
|
19
11
|
|
20
|
-
result = raw_execute(sql, name, async: async)
|
12
|
+
result = raw_execute(sql, name, async: async, allow_retry: allow_retry)
|
21
13
|
ActiveRecord::Result.new(result.fields, result.to_a)
|
22
14
|
end
|
23
15
|
|
@@ -26,7 +18,8 @@ module ActiveRecord
|
|
26
18
|
check_if_write_query(sql)
|
27
19
|
mark_transaction_written_if_write(sql)
|
28
20
|
|
29
|
-
|
21
|
+
sql, _binds = sql_for_insert(sql, pk, binds, returning)
|
22
|
+
raw_execute(sql, name)
|
30
23
|
end
|
31
24
|
|
32
25
|
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
@@ -42,19 +35,27 @@ module ActiveRecord
|
|
42
35
|
|
43
36
|
private
|
44
37
|
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
45
|
-
log(sql, name, async: async) do
|
38
|
+
log(sql, name, async: async) do |notification_payload|
|
46
39
|
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
47
40
|
sync_timezone_changes(conn)
|
48
41
|
result = conn.query(sql)
|
42
|
+
while conn.more_results_exist?
|
43
|
+
conn.next_result
|
44
|
+
end
|
49
45
|
verified!
|
50
46
|
handle_warnings(sql)
|
47
|
+
notification_payload[:row_count] = result.count
|
51
48
|
result
|
52
49
|
end
|
53
50
|
end
|
54
51
|
end
|
55
52
|
|
56
53
|
def last_inserted_id(result)
|
57
|
-
|
54
|
+
if supports_insert_returning?
|
55
|
+
super
|
56
|
+
else
|
57
|
+
result.last_insert_id
|
58
|
+
end
|
58
59
|
end
|
59
60
|
|
60
61
|
def sync_timezone_changes(conn)
|
@@ -71,7 +72,6 @@ module ActiveRecord
|
|
71
72
|
combine_multi_statements(statements).each do |statement|
|
72
73
|
with_raw_connection do |conn|
|
73
74
|
raw_execute(statement, name)
|
74
|
-
conn.next_result while conn.more_results_exist?
|
75
75
|
end
|
76
76
|
end
|
77
77
|
end
|
@@ -90,7 +90,7 @@ module ActiveRecord
|
|
90
90
|
|
91
91
|
yield
|
92
92
|
ensure
|
93
|
-
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
|
93
|
+
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF) if active?
|
94
94
|
end
|
95
95
|
end
|
96
96
|
end
|