activerecord 7.2.0 → 8.0.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 +189 -745
- data/README.rdoc +1 -1
- data/lib/active_record/associations/association.rb +25 -5
- 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/join_dependency/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +5 -5
- 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_assignment.rb +9 -1
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
- data/lib/active_record/attributes.rb +6 -5
- 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 +23 -44
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +53 -18
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +26 -5
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -25
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +20 -38
- 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 +44 -46
- 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/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +50 -6
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +38 -90
- 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 +55 -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_handling.rb +22 -0
- data/lib/active_record/core.rb +16 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
- data/lib/active_record/encryption/encryptor.rb +16 -9
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/encryption.rb +2 -0
- data/lib/active_record/enum.rb +8 -0
- data/lib/active_record/errors.rb +13 -5
- data/lib/active_record/fixtures.rb +0 -1
- 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/migration/command_recorder.rb +22 -5
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +35 -33
- data/lib/active_record/model_schema.rb +6 -3
- data/lib/active_record/nested_attributes.rb +11 -2
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +97 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +6 -6
- data/lib/active_record/railtie.rb +8 -14
- data/lib/active_record/reflection.rb +19 -10
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +135 -75
- data/lib/active_record/relation/calculations.rb +24 -19
- 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 +6 -1
- data/lib/active_record/relation/query_methods.rb +58 -37
- data/lib/active_record/relation/record_fetch_warning.rb +2 -2
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation.rb +72 -61
- data/lib/active_record/result.rb +68 -7
- 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 +6 -2
- data/lib/active_record/statement_cache.rb +12 -12
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +36 -16
- 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 +9 -8
- data/lib/active_record.rb +15 -0
- data/lib/arel/collectors/bind.rb +1 -1
- metadata +14 -14
@@ -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", 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
|
@@ -687,6 +725,8 @@ module ActiveRecord
|
|
687
725
|
end
|
688
726
|
|
689
727
|
basic_structure.map do |column|
|
728
|
+
column = column.to_h
|
729
|
+
|
690
730
|
column_name = column["name"]
|
691
731
|
|
692
732
|
if collation_hash.has_key? column_name
|
@@ -778,12 +818,15 @@ module ActiveRecord
|
|
778
818
|
if @config[:timeout] && @config[:retries]
|
779
819
|
raise ArgumentError, "Cannot specify both timeout and retries arguments"
|
780
820
|
elsif @config[:timeout]
|
781
|
-
|
821
|
+
timeout = self.class.type_cast_config_to_integer(@config[:timeout])
|
822
|
+
raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer)
|
823
|
+
@raw_connection.busy_handler_timeout = timeout
|
782
824
|
elsif @config[:retries]
|
825
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
826
|
+
The retries option is deprecated and will be removed in Rails 8.1. Use timeout instead.
|
827
|
+
MSG
|
783
828
|
retries = self.class.type_cast_config_to_integer(@config[:retries])
|
784
|
-
raw_connection.busy_handler
|
785
|
-
count <= retries
|
786
|
-
end
|
829
|
+
raw_connection.busy_handler { |count| count <= retries }
|
787
830
|
end
|
788
831
|
|
789
832
|
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
|
@@ -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
|