activerecord 7.2.2 → 8.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +239 -878
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -0
- data/lib/active_record/associations/association.rb +34 -10
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +10 -8
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +34 -4
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +2 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/attribute_methods.rb +1 -1
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +0 -9
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +33 -6
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -26
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +21 -39
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +43 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +6 -12
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +59 -16
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +45 -95
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
- data/lib/active_record/connection_adapters.rb +0 -56
- data/lib/active_record/connection_handling.rb +22 -0
- data/lib/active_record/core.rb +18 -14
- data/lib/active_record/database_configurations/database_config.rb +4 -0
- data/lib/active_record/database_configurations/hash_config.rb +8 -0
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +10 -1
- data/lib/active_record/encryption/encryptor.rb +15 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +9 -22
- data/lib/active_record/errors.rb +13 -5
- data/lib/active_record/fixtures.rb +0 -2
- data/lib/active_record/future_result.rb +14 -10
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/insert_all.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -11
- data/lib/active_record/migration/command_recorder.rb +27 -10
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +35 -38
- data/lib/active_record/model_schema.rb +3 -4
- data/lib/active_record/nested_attributes.rb +4 -6
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +102 -50
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +8 -8
- data/lib/active_record/railtie.rb +2 -26
- data/lib/active_record/railties/databases.rake +2 -17
- data/lib/active_record/reflection.rb +18 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +132 -72
- data/lib/active_record/relation/calculations.rb +40 -39
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +18 -18
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +115 -65
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation.rb +79 -61
- data/lib/active_record/result.rb +66 -4
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +5 -0
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/statement_cache.rb +12 -12
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +1 -3
- data/lib/active_record/tasks/database_tasks.rb +48 -47
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +15 -45
- data/lib/arel/collectors/bind.rb +1 -1
- data/lib/arel/table.rb +3 -7
- metadata +11 -12
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -21,87 +21,24 @@ module ActiveRecord
|
|
21
21
|
SQLite3::ExplainPrettyPrinter.new.pp(result)
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
|
26
|
-
check_if_write_query(sql)
|
27
|
-
|
28
|
-
mark_transaction_written_if_write(sql)
|
29
|
-
|
30
|
-
type_casted_binds = type_casted_binds(binds)
|
31
|
-
|
32
|
-
log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
|
33
|
-
with_raw_connection do |conn|
|
34
|
-
# Don't cache statements if they are not prepared
|
35
|
-
unless prepare
|
36
|
-
stmt = conn.prepare(sql)
|
37
|
-
begin
|
38
|
-
cols = stmt.columns
|
39
|
-
unless without_prepared_statement?(binds)
|
40
|
-
stmt.bind_params(type_casted_binds)
|
41
|
-
end
|
42
|
-
records = stmt.to_a
|
43
|
-
ensure
|
44
|
-
stmt.close
|
45
|
-
end
|
46
|
-
else
|
47
|
-
stmt = @statements[sql] ||= conn.prepare(sql)
|
48
|
-
cols = stmt.columns
|
49
|
-
stmt.reset!
|
50
|
-
stmt.bind_params(type_casted_binds)
|
51
|
-
records = stmt.to_a
|
52
|
-
end
|
53
|
-
verified!
|
54
|
-
|
55
|
-
result = build_result(columns: cols, rows: records)
|
56
|
-
notification_payload[:row_count] = result.length
|
57
|
-
result
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def exec_delete(sql, name = "SQL", binds = []) # :nodoc:
|
63
|
-
internal_exec_query(sql, name, binds)
|
64
|
-
@raw_connection.changes
|
24
|
+
def begin_deferred_transaction(isolation = nil) # :nodoc:
|
25
|
+
internal_begin_transaction(:deferred, isolation)
|
65
26
|
end
|
66
|
-
alias :exec_update :exec_delete
|
67
27
|
|
68
28
|
def begin_isolated_db_transaction(isolation) # :nodoc:
|
69
|
-
|
70
|
-
raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
|
71
|
-
|
72
|
-
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
73
|
-
ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted] = conn.get_first_value("PRAGMA read_uncommitted")
|
74
|
-
conn.read_uncommitted = true
|
75
|
-
begin_db_transaction
|
76
|
-
end
|
29
|
+
internal_begin_transaction(:deferred, isolation)
|
77
30
|
end
|
78
31
|
|
79
32
|
def begin_db_transaction # :nodoc:
|
80
|
-
|
81
|
-
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
82
|
-
result = conn.transaction
|
83
|
-
verified!
|
84
|
-
result
|
85
|
-
end
|
86
|
-
end
|
33
|
+
internal_begin_transaction(:immediate, nil)
|
87
34
|
end
|
88
35
|
|
89
36
|
def commit_db_transaction # :nodoc:
|
90
|
-
|
91
|
-
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
92
|
-
conn.commit
|
93
|
-
end
|
94
|
-
end
|
95
|
-
reset_read_uncommitted
|
37
|
+
internal_execute("COMMIT TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
96
38
|
end
|
97
39
|
|
98
40
|
def exec_rollback_db_transaction # :nodoc:
|
99
|
-
|
100
|
-
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
101
|
-
conn.rollback
|
102
|
-
end
|
103
|
-
end
|
104
|
-
reset_read_uncommitted
|
41
|
+
internal_execute("ROLLBACK TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
105
42
|
end
|
106
43
|
|
107
44
|
# https://stackoverflow.com/questions/17574784
|
@@ -113,47 +50,82 @@ module ActiveRecord
|
|
113
50
|
HIGH_PRECISION_CURRENT_TIMESTAMP
|
114
51
|
end
|
115
52
|
|
53
|
+
def execute(...) # :nodoc:
|
54
|
+
# SQLite3Adapter was refactored to use ActiveRecord::Result internally
|
55
|
+
# but for backward compatibility we have to keep returning arrays of hashes here
|
56
|
+
super&.to_a
|
57
|
+
end
|
58
|
+
|
59
|
+
def reset_isolation_level # :nodoc:
|
60
|
+
internal_execute("PRAGMA read_uncommitted=#{@previous_read_uncommitted}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
61
|
+
@previous_read_uncommitted = nil
|
62
|
+
end
|
63
|
+
|
116
64
|
private
|
117
|
-
def
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
65
|
+
def internal_begin_transaction(mode, isolation)
|
66
|
+
if isolation
|
67
|
+
raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
|
68
|
+
raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
|
69
|
+
end
|
70
|
+
|
71
|
+
internal_execute("BEGIN #{mode} TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
72
|
+
if isolation
|
73
|
+
@previous_read_uncommitted = query_value("PRAGMA read_uncommitted")
|
74
|
+
internal_execute("PRAGMA read_uncommitted=ON", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
125
75
|
end
|
126
76
|
end
|
127
77
|
|
128
|
-
def
|
129
|
-
|
130
|
-
|
78
|
+
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
|
79
|
+
if batch
|
80
|
+
raw_connection.execute_batch2(sql)
|
81
|
+
elsif prepare
|
82
|
+
stmt = @statements[sql] ||= raw_connection.prepare(sql)
|
83
|
+
stmt.reset!
|
84
|
+
stmt.bind_params(type_casted_binds)
|
85
|
+
|
86
|
+
result = if stmt.column_count.zero? # No return
|
87
|
+
stmt.step
|
88
|
+
ActiveRecord::Result.empty
|
89
|
+
else
|
90
|
+
ActiveRecord::Result.new(stmt.columns, stmt.to_a)
|
91
|
+
end
|
92
|
+
else
|
93
|
+
# Don't cache statements if they are not prepared.
|
94
|
+
stmt = raw_connection.prepare(sql)
|
95
|
+
begin
|
96
|
+
unless binds.nil? || binds.empty?
|
97
|
+
stmt.bind_params(type_casted_binds)
|
98
|
+
end
|
99
|
+
result = if stmt.column_count.zero? # No return
|
100
|
+
stmt.step
|
101
|
+
ActiveRecord::Result.empty
|
102
|
+
else
|
103
|
+
ActiveRecord::Result.new(stmt.columns, stmt.to_a)
|
104
|
+
end
|
105
|
+
ensure
|
106
|
+
stmt.close
|
107
|
+
end
|
108
|
+
end
|
109
|
+
@last_affected_rows = raw_connection.changes
|
110
|
+
verified!
|
131
111
|
|
132
|
-
|
112
|
+
notification_payload[:row_count] = result&.length || 0
|
113
|
+
result
|
133
114
|
end
|
134
115
|
|
135
|
-
def
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
mark_transaction_written_if_write(sql)
|
116
|
+
def cast_result(result)
|
117
|
+
# Given that SQLite3 doesn't really a Result type, raw_execute already return an ActiveRecord::Result
|
118
|
+
# and we have nothing to cast here.
|
119
|
+
result
|
120
|
+
end
|
141
121
|
|
142
|
-
|
143
|
-
|
144
|
-
result = conn.execute_batch2(sql)
|
145
|
-
verified!
|
146
|
-
notification_payload[:row_count] = result.length
|
147
|
-
result
|
148
|
-
end
|
149
|
-
end
|
122
|
+
def affected_rows(result)
|
123
|
+
@last_affected_rows
|
150
124
|
end
|
151
125
|
|
152
|
-
def
|
153
|
-
|
154
|
-
|
155
|
-
fixtures.map { |fixture| build_fixture_sql([fixture], table_name) }
|
156
|
-
end.compact
|
126
|
+
def execute_batch(statements, name = nil, **kwargs)
|
127
|
+
sql = combine_multi_statements(statements)
|
128
|
+
raw_execute(sql, name, batch: true, **kwargs)
|
157
129
|
end
|
158
130
|
|
159
131
|
def build_truncate_statement(table_name)
|
@@ -163,6 +135,10 @@ module ActiveRecord
|
|
163
135
|
def returning_column_values(result)
|
164
136
|
result.rows.first
|
165
137
|
end
|
138
|
+
|
139
|
+
def default_insert_value(column)
|
140
|
+
column.default
|
141
|
+
end
|
166
142
|
end
|
167
143
|
end
|
168
144
|
end
|
@@ -5,12 +5,6 @@ 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
8
|
def visit_ForeignKeyDefinition(o)
|
15
9
|
super.dup.tap do |sql|
|
16
10
|
sql << " DEFERRABLE INITIALLY #{o.deferrable.to_s.upcase}" if o.deferrable
|
@@ -5,6 +5,19 @@ module ActiveRecord
|
|
5
5
|
module SQLite3
|
6
6
|
class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
|
7
7
|
private
|
8
|
+
def virtual_tables(stream)
|
9
|
+
virtual_tables = @connection.virtual_tables
|
10
|
+
if virtual_tables.any?
|
11
|
+
stream.puts
|
12
|
+
stream.puts " # Virtual tables defined in this database."
|
13
|
+
stream.puts " # Note that virtual tables may not work with other database engines. Be careful if changing database."
|
14
|
+
virtual_tables.sort.each do |table_name, options|
|
15
|
+
module_name, arguments = options
|
16
|
+
stream.puts " create_virtual_table #{table_name.inspect}, #{module_name.inspect}, #{arguments.split(", ").inspect}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
8
21
|
def default_primary_key?(column)
|
9
22
|
schema_type(column) == :integer
|
10
23
|
end
|
@@ -82,6 +82,10 @@ module ActiveRecord
|
|
82
82
|
alter_table(from_table, foreign_keys)
|
83
83
|
end
|
84
84
|
|
85
|
+
def virtual_table_exists?(table_name)
|
86
|
+
query_values(data_source_sql(table_name, type: "VIRTUAL TABLE"), "SCHEMA").any?
|
87
|
+
end
|
88
|
+
|
85
89
|
def check_constraints(table_name)
|
86
90
|
table_sql = query_value(<<-SQL, "SCHEMA")
|
87
91
|
SELECT sql
|
@@ -176,7 +180,8 @@ module ActiveRecord
|
|
176
180
|
scope = quoted_scope(name, type: type)
|
177
181
|
scope[:type] ||= "'table','view'"
|
178
182
|
|
179
|
-
sql = +"SELECT name FROM
|
183
|
+
sql = +"SELECT name FROM pragma_table_list WHERE schema <> 'temp'"
|
184
|
+
sql << " AND name NOT IN ('sqlite_sequence', 'sqlite_schema')"
|
180
185
|
sql << " AND name = #{scope[:name]}" if scope[:name]
|
181
186
|
sql << " AND type IN (#{scope[:type]})"
|
182
187
|
sql
|
@@ -189,6 +194,8 @@ module ActiveRecord
|
|
189
194
|
"'table'"
|
190
195
|
when "VIEW"
|
191
196
|
"'view'"
|
197
|
+
when "VIRTUAL TABLE"
|
198
|
+
"'virtual'"
|
192
199
|
end
|
193
200
|
scope = {}
|
194
201
|
scope[:name] = quote(name) if name
|
@@ -11,9 +11,12 @@ 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", ">= 1
|
14
|
+
gem "sqlite3", ">= 2.1"
|
15
15
|
require "sqlite3"
|
16
16
|
|
17
|
+
# Suppress the warning that SQLite3 issues when open writable connections are carried across fork()
|
18
|
+
SQLite3::ForkSafety.suppress_warnings!
|
19
|
+
|
17
20
|
module ActiveRecord
|
18
21
|
module ConnectionAdapters # :nodoc:
|
19
22
|
# = Active Record SQLite3 Adapter
|
@@ -45,7 +48,7 @@ module ActiveRecord
|
|
45
48
|
args << "-header" if options[:header]
|
46
49
|
args << File.expand_path(config.database, Rails.respond_to?(:root) ? Rails.root : nil)
|
47
50
|
|
48
|
-
find_cmd_and_exec(
|
51
|
+
find_cmd_and_exec(ActiveRecord.database_cli[:sqlite], *args)
|
49
52
|
end
|
50
53
|
end
|
51
54
|
|
@@ -119,9 +122,14 @@ module ActiveRecord
|
|
119
122
|
end
|
120
123
|
end
|
121
124
|
|
125
|
+
@last_affected_rows = nil
|
126
|
+
@previous_read_uncommitted = nil
|
122
127
|
@config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
|
123
|
-
@connection_parameters = @config.merge(
|
124
|
-
|
128
|
+
@connection_parameters = @config.merge(
|
129
|
+
database: @config[:database].to_s,
|
130
|
+
results_as_hash: true,
|
131
|
+
default_transaction_mode: :immediate,
|
132
|
+
)
|
125
133
|
end
|
126
134
|
|
127
135
|
def database_exists?
|
@@ -278,6 +286,38 @@ module ActiveRecord
|
|
278
286
|
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
279
287
|
end
|
280
288
|
|
289
|
+
VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.+)\)/i
|
290
|
+
|
291
|
+
# Returns a list of defined virtual tables
|
292
|
+
def virtual_tables
|
293
|
+
query = <<~SQL
|
294
|
+
SELECT name, sql FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL %';
|
295
|
+
SQL
|
296
|
+
|
297
|
+
exec_query(query, "SCHEMA").cast_values.each_with_object({}) do |row, memo|
|
298
|
+
table_name, sql = row[0], row[1]
|
299
|
+
_, module_name, arguments = sql.match(VIRTUAL_TABLE_REGEX).to_a
|
300
|
+
memo[table_name] = [module_name, arguments]
|
301
|
+
end.to_a
|
302
|
+
end
|
303
|
+
|
304
|
+
# Creates a virtual table
|
305
|
+
#
|
306
|
+
# Example:
|
307
|
+
# create_virtual_table :emails, :fts5, ['sender', 'title',' body']
|
308
|
+
def create_virtual_table(table_name, module_name, values)
|
309
|
+
exec_query "CREATE VIRTUAL TABLE IF NOT EXISTS #{table_name} USING #{module_name} (#{values.join(", ")})"
|
310
|
+
end
|
311
|
+
|
312
|
+
# Drops a virtual table
|
313
|
+
#
|
314
|
+
# Although this command ignores +module_name+ and +values+,
|
315
|
+
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
316
|
+
# In that case, +module_name+, +values+ and +options+ will be used by #create_virtual_table.
|
317
|
+
def drop_virtual_table(table_name, module_name, values, **options)
|
318
|
+
drop_table(table_name)
|
319
|
+
end
|
320
|
+
|
281
321
|
# Renames a table.
|
282
322
|
#
|
283
323
|
# Example:
|
@@ -428,10 +468,6 @@ module ActiveRecord
|
|
428
468
|
@config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
|
429
469
|
end
|
430
470
|
|
431
|
-
def use_insert_returning?
|
432
|
-
@use_insert_returning
|
433
|
-
end
|
434
|
-
|
435
471
|
def get_database_version # :nodoc:
|
436
472
|
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
|
437
473
|
end
|
@@ -661,6 +697,8 @@ module ActiveRecord
|
|
661
697
|
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
662
698
|
elsif exception.message.match?(/called on a closed database/i)
|
663
699
|
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
700
|
+
elsif exception.is_a?(::SQLite3::BusyException)
|
701
|
+
StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
664
702
|
else
|
665
703
|
super
|
666
704
|
end
|
@@ -778,12 +816,15 @@ module ActiveRecord
|
|
778
816
|
if @config[:timeout] && @config[:retries]
|
779
817
|
raise ArgumentError, "Cannot specify both timeout and retries arguments"
|
780
818
|
elsif @config[:timeout]
|
781
|
-
|
819
|
+
timeout = self.class.type_cast_config_to_integer(@config[:timeout])
|
820
|
+
raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer)
|
821
|
+
@raw_connection.busy_handler_timeout = timeout
|
782
822
|
elsif @config[:retries]
|
823
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
824
|
+
The retries option is deprecated and will be removed in Rails 8.1. Use timeout instead.
|
825
|
+
MSG
|
783
826
|
retries = self.class.type_cast_config_to_integer(@config[:retries])
|
784
|
-
raw_connection.busy_handler
|
785
|
-
count <= retries
|
786
|
-
end
|
827
|
+
raw_connection.busy_handler { |count| count <= retries }
|
787
828
|
end
|
788
829
|
|
789
830
|
super
|
@@ -4,93 +4,63 @@ module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
5
5
|
module Trilogy
|
6
6
|
module DatabaseStatements
|
7
|
-
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
|
8
|
-
sql = transform_query(sql)
|
9
|
-
check_if_write_query(sql)
|
10
|
-
mark_transaction_written_if_write(sql)
|
11
|
-
|
12
|
-
result = raw_execute(sql, name, async: async, allow_retry: allow_retry)
|
13
|
-
ActiveRecord::Result.new(result.fields, result.to_a)
|
14
|
-
end
|
15
|
-
|
16
7
|
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil, returning: nil) # :nodoc:
|
17
|
-
sql = transform_query(sql)
|
18
|
-
check_if_write_query(sql)
|
19
|
-
mark_transaction_written_if_write(sql)
|
20
|
-
|
21
8
|
sql, _binds = sql_for_insert(sql, pk, binds, returning)
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
26
|
-
sql = transform_query(sql)
|
27
|
-
check_if_write_query(sql)
|
28
|
-
mark_transaction_written_if_write(sql)
|
29
|
-
|
30
|
-
result = raw_execute(to_sql(sql, binds), name)
|
31
|
-
result.affected_rows
|
9
|
+
internal_execute(sql, name)
|
32
10
|
end
|
33
11
|
|
34
|
-
alias :exec_update :exec_delete # :nodoc:
|
35
|
-
|
36
12
|
private
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
result = conn.query(sql)
|
42
|
-
while conn.more_results_exist?
|
43
|
-
conn.next_result
|
44
|
-
end
|
45
|
-
verified!
|
46
|
-
handle_warnings(sql)
|
47
|
-
notification_payload[:row_count] = result.count
|
48
|
-
result
|
49
|
-
end
|
13
|
+
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
|
14
|
+
reset_multi_statement = if batch && !@config[:multi_statement]
|
15
|
+
raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
|
16
|
+
true
|
50
17
|
end
|
51
|
-
end
|
52
18
|
|
53
|
-
|
54
|
-
|
55
|
-
|
19
|
+
# Make sure we carry over any changes to ActiveRecord.default_timezone that have been
|
20
|
+
# made since we established the connection
|
21
|
+
if default_timezone == :local
|
22
|
+
raw_connection.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
56
23
|
else
|
57
|
-
|
24
|
+
raw_connection.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
58
25
|
end
|
59
|
-
end
|
60
26
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
27
|
+
result = raw_connection.query(sql)
|
28
|
+
while raw_connection.more_results_exist?
|
29
|
+
raw_connection.next_result
|
30
|
+
end
|
31
|
+
verified!
|
32
|
+
handle_warnings(sql)
|
33
|
+
notification_payload[:row_count] = result.count
|
34
|
+
result
|
35
|
+
ensure
|
36
|
+
if reset_multi_statement && active?
|
37
|
+
raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
|
67
38
|
end
|
68
39
|
end
|
69
40
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
41
|
+
def cast_result(result)
|
42
|
+
if result.fields.empty?
|
43
|
+
ActiveRecord::Result.empty
|
44
|
+
else
|
45
|
+
ActiveRecord::Result.new(result.fields, result.rows)
|
76
46
|
end
|
77
47
|
end
|
78
48
|
|
79
|
-
def
|
80
|
-
|
49
|
+
def affected_rows(result)
|
50
|
+
result.affected_rows
|
81
51
|
end
|
82
52
|
|
83
|
-
def
|
84
|
-
if
|
85
|
-
|
53
|
+
def last_inserted_id(result)
|
54
|
+
if supports_insert_returning?
|
55
|
+
super
|
56
|
+
else
|
57
|
+
result.last_insert_id
|
86
58
|
end
|
59
|
+
end
|
87
60
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
yield
|
92
|
-
ensure
|
93
|
-
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF) if active?
|
61
|
+
def execute_batch(statements, name = nil, **kwargs)
|
62
|
+
combine_multi_statements(statements).each do |statement|
|
63
|
+
raw_execute(statement, name, batch: true, **kwargs)
|
94
64
|
end
|
95
65
|
end
|
96
66
|
end
|
@@ -149,23 +149,6 @@ module ActiveRecord
|
|
149
149
|
TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
|
150
150
|
end
|
151
151
|
|
152
|
-
def each_hash(result)
|
153
|
-
return to_enum(:each_hash, result) unless block_given?
|
154
|
-
|
155
|
-
keys = result.fields.map(&:to_sym)
|
156
|
-
result.rows.each do |row|
|
157
|
-
hash = {}
|
158
|
-
idx = 0
|
159
|
-
row.each do |value|
|
160
|
-
hash[keys[idx]] = value
|
161
|
-
idx += 1
|
162
|
-
end
|
163
|
-
yield hash
|
164
|
-
end
|
165
|
-
|
166
|
-
nil
|
167
|
-
end
|
168
|
-
|
169
152
|
def error_number(exception)
|
170
153
|
exception.error_code if exception.respond_to?(:error_code)
|
171
154
|
end
|
@@ -31,62 +31,6 @@ module ActiveRecord
|
|
31
31
|
class_name, path_to_adapter = @adapters[adapter_name.to_s]
|
32
32
|
|
33
33
|
unless class_name
|
34
|
-
# To provide better error messages for adapters expecting the pre-7.2 adapter registration API, we attempt
|
35
|
-
# to load the adapter file from the old location which was required by convention, and then raise an error
|
36
|
-
# describing how to upgrade the adapter to the new API.
|
37
|
-
legacy_adapter_path = "active_record/connection_adapters/#{adapter_name}_adapter"
|
38
|
-
legacy_adapter_connection_method_name = "#{adapter_name}_connection".to_sym
|
39
|
-
|
40
|
-
begin
|
41
|
-
require legacy_adapter_path
|
42
|
-
# If we reach here it means we found the found a file that may be the legacy adapter and should raise.
|
43
|
-
if ActiveRecord::ConnectionHandling.method_defined?(legacy_adapter_connection_method_name)
|
44
|
-
# If we find the connection method then we care certain it is a legacy adapter.
|
45
|
-
deprecation_message = <<~MSG.squish
|
46
|
-
Database configuration specifies '#{adapter_name}' adapter but that adapter has not been registered.
|
47
|
-
Rails 7.2 has changed the way Active Record database adapters are loaded. The adapter needs to be
|
48
|
-
updated to register itself rather than being loaded by convention.
|
49
|
-
Ensure that the adapter in the Gemfile is at the latest version. If it is, then the adapter may need to
|
50
|
-
be modified.
|
51
|
-
See:
|
52
|
-
https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-c-register
|
53
|
-
MSG
|
54
|
-
|
55
|
-
exception_message = <<~MSG.squish
|
56
|
-
Database configuration specifies '#{adapter_name}' adapter but that adapter has not been registered.
|
57
|
-
Ensure that the adapter in the Gemfile is at the latest version. If it is, then the adapter may need to
|
58
|
-
be modified.
|
59
|
-
MSG
|
60
|
-
else
|
61
|
-
# If we do not find the connection method we are much less certain it is a legacy adapter. Even though the
|
62
|
-
# file exists in the location defined by convenntion, it does not necessarily mean that file is supposed
|
63
|
-
# to define the adapter the legacy way. So raise an error that explains both possibilities.
|
64
|
-
deprecation_message = <<~MSG.squish
|
65
|
-
Database configuration specifies nonexistent '#{adapter_name}' adapter.
|
66
|
-
Available adapters are: #{@adapters.keys.sort.join(", ")}.
|
67
|
-
Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary
|
68
|
-
adapter gem to your Gemfile if it's not in the list of available adapters.
|
69
|
-
Rails 7.2 has changed the way Active Record database adapters are loaded. Ensure that the adapter in
|
70
|
-
the Gemfile is at the latest version. If it is up to date, the adapter may need to be modified.
|
71
|
-
See:
|
72
|
-
https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-c-register
|
73
|
-
MSG
|
74
|
-
|
75
|
-
exception_message = <<~MSG.squish
|
76
|
-
Database configuration specifies nonexistent '#{adapter_name}' adapter.
|
77
|
-
Available adapters are: #{@adapters.keys.sort.join(", ")}.
|
78
|
-
Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary
|
79
|
-
adapter gem to your Gemfile and that it is at its latest version. If it is up to date, the adapter may
|
80
|
-
need to be modified.
|
81
|
-
MSG
|
82
|
-
end
|
83
|
-
|
84
|
-
ActiveRecord.deprecator.warn(deprecation_message)
|
85
|
-
raise AdapterNotFound, exception_message
|
86
|
-
rescue LoadError => error
|
87
|
-
# The adapter was not found in the legacy location so fall through to the error handling for a missing adapter.
|
88
|
-
end
|
89
|
-
|
90
34
|
raise AdapterNotFound, <<~MSG.squish
|
91
35
|
Database configuration specifies nonexistent '#{adapter_name}' adapter.
|
92
36
|
Available adapters are: #{@adapters.keys.sort.join(", ")}.
|
@@ -87,6 +87,8 @@ module ActiveRecord
|
|
87
87
|
|
88
88
|
connections = []
|
89
89
|
|
90
|
+
@shard_keys = shards.keys
|
91
|
+
|
90
92
|
if shards.empty?
|
91
93
|
shards[:default] = database
|
92
94
|
end
|
@@ -175,6 +177,18 @@ module ActiveRecord
|
|
175
177
|
connected_to_stack.pop
|
176
178
|
end
|
177
179
|
|
180
|
+
# Passes the block to +connected_to+ for every +shard+ the
|
181
|
+
# model is configured to connect to (if any), and returns the
|
182
|
+
# results in an array.
|
183
|
+
#
|
184
|
+
# Optionally, +role+ and/or +prevent_writes+ can be passed which
|
185
|
+
# will be forwarded to each +connected_to+ call.
|
186
|
+
def connected_to_all_shards(role: nil, prevent_writes: false, &blk)
|
187
|
+
shard_keys.map do |shard|
|
188
|
+
connected_to(shard: shard, role: role, prevent_writes: prevent_writes, &blk)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
178
192
|
# Use a specified connection.
|
179
193
|
#
|
180
194
|
# This method is useful for ensuring that a specific connection is
|
@@ -359,6 +373,14 @@ module ActiveRecord
|
|
359
373
|
connection_pool.schema_cache.clear!
|
360
374
|
end
|
361
375
|
|
376
|
+
def shard_keys
|
377
|
+
connection_class_for_self.instance_variable_get(:@shard_keys) || []
|
378
|
+
end
|
379
|
+
|
380
|
+
def sharded?
|
381
|
+
shard_keys.any?
|
382
|
+
end
|
383
|
+
|
362
384
|
private
|
363
385
|
def resolve_config_for_connection(config_or_env)
|
364
386
|
raise "Anonymous class is not allowed." unless name
|