activerecord 7.1.5 → 7.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +515 -2440
- 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
|