activerecord 7.1.5.1 → 7.2.0.beta1
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 -2445
- 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 +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +14 -7
- 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 +6 -4
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +29 -28
- data/lib/active_record/associations/join_dependency.rb +5 -5
- 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 +33 -16
- data/lib/active_record/attribute_assignment.rb +1 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +1 -1
- 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 +7 -10
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +60 -71
- data/lib/active_record/attributes.rb +55 -42
- data/lib/active_record/autosave_association.rb +13 -32
- data/lib/active_record/base.rb +2 -3
- 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 +248 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
- 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 +14 -5
- data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
- 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 +7 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
- 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 +1 -1
- 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 +15 -13
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
- 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 +107 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +53 -37
- data/lib/active_record/counter_cache.rb +18 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +15 -4
- data/lib/active_record/database_configurations/hash_config.rb +38 -34
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +24 -0
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +2 -2
- data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
- data/lib/active_record/encryption/encryptor.rb +17 -2
- 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.rb +0 -2
- data/lib/active_record/enum.rb +10 -1
- data/lib/active_record/errors.rb +16 -11
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +8 -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 +7 -6
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +1 -4
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +5 -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 +28 -68
- data/lib/active_record/nested_attributes.rb +13 -16
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +50 -62
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +41 -44
- data/lib/active_record/reflection.rb +90 -35
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +94 -61
- 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.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +196 -57
- 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 +496 -72
- data/lib/active_record/result.rb +31 -44
- 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/signed_id.rb +11 -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 +76 -70
- 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 +81 -91
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +1 -1
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +68 -0
- data/lib/active_record/transactions.rb +43 -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 +14 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +149 -40
- 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 +3 -2
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- metadata +20 -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
|
@@ -15,16 +15,6 @@ 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
|
|
@@ -290,6 +291,7 @@ module ActiveRecord
|
|
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,12 @@ module ActiveRecord
|
|
454
472
|
end
|
455
473
|
|
456
474
|
def table_structure(table_name)
|
457
|
-
structure =
|
458
|
-
|
475
|
+
structure = if supports_virtual_columns?
|
476
|
+
internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
|
477
|
+
else
|
478
|
+
internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
479
|
+
end
|
480
|
+
raise ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'", connection_pool: @pool) if structure.empty?
|
459
481
|
table_structure_with_collation(table_name, structure)
|
460
482
|
end
|
461
483
|
alias column_definitions table_structure
|
@@ -494,8 +516,9 @@ module ActiveRecord
|
|
494
516
|
# See: https://www.sqlite.org/lang_altertable.html
|
495
517
|
# SQLite has an additional restriction on the ALTER TABLE statement
|
496
518
|
def invalid_alter_table_type?(type, options)
|
497
|
-
type
|
498
|
-
options[:null] == false && options[:default].nil?
|
519
|
+
type == :primary_key || options[:primary_key] ||
|
520
|
+
options[:null] == false && options[:default].nil? ||
|
521
|
+
(type == :virtual && options[:stored])
|
499
522
|
end
|
500
523
|
|
501
524
|
def alter_table(
|
@@ -551,12 +574,6 @@ module ActiveRecord
|
|
551
574
|
options[:rename][column.name.to_sym] ||
|
552
575
|
column.name) : column.name
|
553
576
|
|
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
577
|
column_options = {
|
561
578
|
limit: column.limit,
|
562
579
|
precision: column.precision,
|
@@ -566,19 +583,31 @@ module ActiveRecord
|
|
566
583
|
primary_key: column_name == from_primary_key
|
567
584
|
}
|
568
585
|
|
569
|
-
|
570
|
-
column_options[:
|
586
|
+
if column.virtual?
|
587
|
+
column_options[:as] = column.default_function
|
588
|
+
column_options[:stored] = column.virtual_stored?
|
589
|
+
column_options[:type] = column.type
|
590
|
+
elsif column.has_default?
|
591
|
+
type = lookup_cast_type_from_column(column)
|
592
|
+
default = type.deserialize(column.default)
|
593
|
+
default = -> { column.default_function } if default.nil?
|
594
|
+
|
595
|
+
unless column.auto_increment?
|
596
|
+
column_options[:default] = default
|
597
|
+
end
|
571
598
|
end
|
572
599
|
|
573
|
-
column_type = column.bigint? ? :bigint : column.type
|
600
|
+
column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type)
|
574
601
|
@definition.column(column_name, column_type, **column_options)
|
575
602
|
end
|
576
603
|
|
577
604
|
yield @definition if block_given?
|
578
605
|
end
|
579
606
|
copy_table_indexes(from, to, options[:rename] || {})
|
607
|
+
|
608
|
+
columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name)
|
580
609
|
copy_table_contents(from, to,
|
581
|
-
|
610
|
+
columns_to_copy,
|
582
611
|
options[:rename] || {})
|
583
612
|
end
|
584
613
|
|
@@ -643,32 +672,22 @@ module ActiveRecord
|
|
643
672
|
|
644
673
|
COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
|
645
674
|
PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i
|
675
|
+
GENERATED_ALWAYS_AS_REGEX = /.*"(\w+)".+GENERATED ALWAYS AS \((.+)\) (?:STORED|VIRTUAL)/i
|
646
676
|
|
647
677
|
def table_structure_with_collation(table_name, basic_structure)
|
648
678
|
collation_hash = {}
|
649
679
|
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
|
656
|
-
|
657
|
-
# Result will have following sample string
|
658
|
-
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
659
|
-
# "password_digest" varchar COLLATE "NOCASE");
|
660
|
-
result = query_value(sql, "SCHEMA")
|
680
|
+
generated_columns = {}
|
661
681
|
|
662
|
-
|
663
|
-
# Splitting with left parentheses and discarding the first part will return all
|
664
|
-
# columns separated with comma(,).
|
665
|
-
columns_string = result.split("(", 2).last
|
682
|
+
column_strings = table_structure_sql(table_name)
|
666
683
|
|
667
|
-
|
684
|
+
if column_strings.any?
|
685
|
+
column_strings.each do |column_string|
|
668
686
|
# This regex will match the column name and collation type and will save
|
669
687
|
# the value in $1 and $2 respectively.
|
670
688
|
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
671
689
|
auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
|
690
|
+
generated_columns[$1] = $2 if GENERATED_ALWAYS_AS_REGEX =~ column_string
|
672
691
|
end
|
673
692
|
|
674
693
|
basic_structure.map do |column|
|
@@ -682,6 +701,10 @@ module ActiveRecord
|
|
682
701
|
column["auto_increment"] = true
|
683
702
|
end
|
684
703
|
|
704
|
+
if generated_columns.has_key?(column_name)
|
705
|
+
column["dflt_value"] = generated_columns[column_name]
|
706
|
+
end
|
707
|
+
|
685
708
|
column
|
686
709
|
end
|
687
710
|
else
|
@@ -689,6 +712,28 @@ module ActiveRecord
|
|
689
712
|
end
|
690
713
|
end
|
691
714
|
|
715
|
+
def table_structure_sql(table_name)
|
716
|
+
sql = <<~SQL
|
717
|
+
SELECT sql FROM
|
718
|
+
(SELECT * FROM sqlite_master UNION ALL
|
719
|
+
SELECT * FROM sqlite_temp_master)
|
720
|
+
WHERE type = 'table' AND name = #{quote(table_name)}
|
721
|
+
SQL
|
722
|
+
|
723
|
+
# Result will have following sample string
|
724
|
+
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
725
|
+
# "password_digest" varchar COLLATE "NOCASE");
|
726
|
+
result = query_value(sql, "SCHEMA")
|
727
|
+
|
728
|
+
return [] unless result
|
729
|
+
|
730
|
+
# Splitting with left parentheses and discarding the first part will return all
|
731
|
+
# columns separated with comma(,).
|
732
|
+
columns_string = result.split("(", 2).last
|
733
|
+
|
734
|
+
columns_string.split(",").map(&:strip)
|
735
|
+
end
|
736
|
+
|
692
737
|
def arel_visitor
|
693
738
|
Arel::Visitors::SQLite.new(self)
|
694
739
|
end
|
@@ -723,29 +768,16 @@ module ActiveRecord
|
|
723
768
|
end
|
724
769
|
end
|
725
770
|
|
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")
|
771
|
+
super
|
772
|
+
|
773
|
+
pragmas = @config.fetch(:pragmas, {}).stringify_keys
|
774
|
+
DEFAULT_PRAGMAS.merge(pragmas).each do |pragma, value|
|
775
|
+
if ::SQLite3::Pragmas.method_defined?("#{pragma}=")
|
776
|
+
@raw_connection.public_send("#{pragma}=", value)
|
777
|
+
else
|
778
|
+
warn "Unknown SQLite pragma: #{pragma}"
|
779
|
+
end
|
742
780
|
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
781
|
end
|
750
782
|
end
|
751
783
|
ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
|
@@ -12,12 +12,12 @@ module ActiveRecord
|
|
12
12
|
result
|
13
13
|
end
|
14
14
|
|
15
|
-
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
|
15
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
|
16
16
|
sql = transform_query(sql)
|
17
17
|
check_if_write_query(sql)
|
18
18
|
mark_transaction_written_if_write(sql)
|
19
19
|
|
20
|
-
result = raw_execute(sql, name, async: async)
|
20
|
+
result = raw_execute(sql, name, async: async, allow_retry: allow_retry)
|
21
21
|
ActiveRecord::Result.new(result.fields, result.to_a)
|
22
22
|
end
|
23
23
|
|
@@ -26,7 +26,8 @@ module ActiveRecord
|
|
26
26
|
check_if_write_query(sql)
|
27
27
|
mark_transaction_written_if_write(sql)
|
28
28
|
|
29
|
-
|
29
|
+
sql, _binds = sql_for_insert(sql, pk, binds, returning)
|
30
|
+
raw_execute(sql, name)
|
30
31
|
end
|
31
32
|
|
32
33
|
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
@@ -42,19 +43,24 @@ module ActiveRecord
|
|
42
43
|
|
43
44
|
private
|
44
45
|
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
45
|
-
log(sql, name, async: async) do
|
46
|
+
log(sql, name, async: async) do |notification_payload|
|
46
47
|
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
47
48
|
sync_timezone_changes(conn)
|
48
49
|
result = conn.query(sql)
|
49
50
|
verified!
|
50
51
|
handle_warnings(sql)
|
52
|
+
notification_payload[:row_count] = result.count
|
51
53
|
result
|
52
54
|
end
|
53
55
|
end
|
54
56
|
end
|
55
57
|
|
56
58
|
def last_inserted_id(result)
|
57
|
-
|
59
|
+
if supports_insert_returning?
|
60
|
+
super
|
61
|
+
else
|
62
|
+
result.last_insert_id
|
63
|
+
end
|
58
64
|
end
|
59
65
|
|
60
66
|
def sync_timezone_changes(conn)
|
@@ -90,7 +96,7 @@ module ActiveRecord
|
|
90
96
|
|
91
97
|
yield
|
92
98
|
ensure
|
93
|
-
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
|
99
|
+
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF) if active?
|
94
100
|
end
|
95
101
|
end
|
96
102
|
end
|
@@ -2,44 +2,17 @@
|
|
2
2
|
|
3
3
|
require "active_record/connection_adapters/abstract_mysql_adapter"
|
4
4
|
|
5
|
-
gem "trilogy", "~> 2.
|
5
|
+
gem "trilogy", "~> 2.7"
|
6
6
|
require "trilogy"
|
7
7
|
|
8
8
|
require "active_record/connection_adapters/trilogy/database_statements"
|
9
9
|
|
10
10
|
module ActiveRecord
|
11
|
-
module ConnectionHandling # :nodoc:
|
12
|
-
def trilogy_adapter_class
|
13
|
-
ConnectionAdapters::TrilogyAdapter
|
14
|
-
end
|
15
|
-
|
16
|
-
# Establishes a connection to the database that's used by all Active Record objects.
|
17
|
-
def trilogy_connection(config)
|
18
|
-
configuration = config.dup
|
19
|
-
|
20
|
-
# Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows
|
21
|
-
# matched rather than number of rows updated.
|
22
|
-
configuration[:found_rows] = true
|
23
|
-
|
24
|
-
options = [
|
25
|
-
configuration[:host],
|
26
|
-
configuration[:port],
|
27
|
-
configuration[:database],
|
28
|
-
configuration[:username],
|
29
|
-
configuration[:password],
|
30
|
-
configuration[:socket],
|
31
|
-
0
|
32
|
-
]
|
33
|
-
|
34
|
-
trilogy_adapter_class.new nil, logger, options, configuration
|
35
|
-
end
|
36
|
-
end
|
37
11
|
module ConnectionAdapters
|
38
12
|
class TrilogyAdapter < AbstractMysqlAdapter
|
39
13
|
ER_BAD_DB_ERROR = 1049
|
40
14
|
ER_DBACCESS_DENIED_ERROR = 1044
|
41
15
|
ER_ACCESS_DENIED_ERROR = 1045
|
42
|
-
ER_SERVER_SHUTDOWN = 1053
|
43
16
|
|
44
17
|
ADAPTER_NAME = "Trilogy"
|
45
18
|
|
@@ -99,16 +72,22 @@ module ActiveRecord
|
|
99
72
|
end
|
100
73
|
end
|
101
74
|
|
102
|
-
def initialize(
|
103
|
-
|
75
|
+
def initialize(config, *)
|
76
|
+
config = config.dup
|
77
|
+
|
78
|
+
# Trilogy ignores `socket` if `host is set. We want the opposite to allow
|
79
|
+
# configuring UNIX domain sockets via `DATABASE_URL`.
|
80
|
+
config.delete(:host) if config[:socket]
|
104
81
|
|
105
|
-
|
82
|
+
# Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows
|
83
|
+
# matched rather than number of rows updated.
|
84
|
+
config[:found_rows] = true
|
85
|
+
|
86
|
+
if config[:prepared_statements]
|
106
87
|
raise ArgumentError, "Trilogy currently doesn't support prepared statements. Remove `prepared_statements: true` from your database configuration."
|
107
88
|
end
|
108
89
|
|
109
|
-
|
110
|
-
# configuring UNIX domain sockets via `DATABASE_URL`.
|
111
|
-
@config.delete(:host) if @config[:socket]
|
90
|
+
super
|
112
91
|
end
|
113
92
|
|
114
93
|
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
|
@@ -137,14 +116,12 @@ module ActiveRecord
|
|
137
116
|
true
|
138
117
|
end
|
139
118
|
|
140
|
-
def
|
141
|
-
|
142
|
-
conn.escape(string)
|
143
|
-
end
|
119
|
+
def connected?
|
120
|
+
!(@raw_connection.nil? || @raw_connection.closed?)
|
144
121
|
end
|
145
122
|
|
146
123
|
def active?
|
147
|
-
|
124
|
+
connected? && @lock.synchronize { @raw_connection&.ping } || false
|
148
125
|
rescue ::Trilogy::Error
|
149
126
|
false
|
150
127
|
end
|
@@ -206,7 +183,7 @@ module ActiveRecord
|
|
206
183
|
end
|
207
184
|
|
208
185
|
def full_version
|
209
|
-
|
186
|
+
database_version.full_version_string
|
210
187
|
end
|
211
188
|
|
212
189
|
def get_full_version
|
@@ -219,18 +196,12 @@ module ActiveRecord
|
|
219
196
|
if exception.is_a?(::Trilogy::TimeoutError) && !exception.error_code
|
220
197
|
return ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
221
198
|
end
|
222
|
-
error_code = exception.error_code if exception.respond_to?(:error_code)
|
223
|
-
|
224
|
-
case error_code
|
225
|
-
when ER_SERVER_SHUTDOWN
|
226
|
-
return ConnectionFailed.new(message, connection_pool: @pool)
|
227
|
-
end
|
228
199
|
|
229
200
|
case exception
|
230
|
-
when
|
201
|
+
when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError
|
231
202
|
return ConnectionFailed.new(message, connection_pool: @pool)
|
232
203
|
when ::Trilogy::Error
|
233
|
-
if
|
204
|
+
if exception.is_a?(SystemCallError) || exception.message.include?("TRILOGY_INVALID_SEQUENCE_ID")
|
234
205
|
return ConnectionFailed.new(message, connection_pool: @pool)
|
235
206
|
end
|
236
207
|
end
|