activerecord 7.2.3.1 → 8.0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +475 -946
- 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 +1 -1
- 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.rb +2 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- 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 +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
- 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 +8 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
- 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 +37 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -74
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +21 -40
- 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 +50 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
- 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 +53 -96
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +9 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- 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 +23 -1
- data/lib/active_record/core.rb +29 -14
- data/lib/active_record/database_configurations/database_config.rb +4 -0
- data/lib/active_record/database_configurations/hash_config.rb +16 -2
- 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 +16 -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 +15 -9
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/insert_all.rb +3 -3
- 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 +32 -12
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +38 -42
- 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 +11 -35
- 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/array_handler.rb +3 -1
- 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 +105 -61
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +1 -1
- data/lib/active_record/relation.rb +80 -62
- 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 +14 -14
- 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 +69 -60
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transactions.rb +5 -6
- data/lib/active_record/type/serialized.rb +5 -0
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +21 -48
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/table.rb +3 -7
- metadata +10 -11
- 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 have a Result type, raw_execute already returns an ActiveRecord::Result
|
|
118
|
+
# so 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,14 @@ 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
|
+
if column.default_function
|
|
141
|
+
Arel.sql(column.default_function)
|
|
142
|
+
else
|
|
143
|
+
column.default
|
|
144
|
+
end
|
|
145
|
+
end
|
|
166
146
|
end
|
|
167
147
|
end
|
|
168
148
|
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,12 +5,37 @@ 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.reject { |name, _| ignored?(name) }
|
|
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
|
-
schema_type(column) == :integer
|
|
22
|
+
schema_type(column) == :integer && primary_key_has_autoincrement?
|
|
10
23
|
end
|
|
11
24
|
|
|
12
25
|
def explicit_primary_key_default?(column)
|
|
13
|
-
column.bigint?
|
|
26
|
+
column.bigint? || (column.type == :integer && !primary_key_has_autoincrement?)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def primary_key_has_autoincrement?
|
|
30
|
+
return false unless table_name
|
|
31
|
+
|
|
32
|
+
table_sql = @connection.query_value(<<~SQL, "SCHEMA")
|
|
33
|
+
SELECT sql FROM sqlite_master WHERE name = #{@connection.quote(table_name)} AND type = 'table'
|
|
34
|
+
UNION ALL
|
|
35
|
+
SELECT sql FROM sqlite_temp_master WHERE name = #{@connection.quote(table_name)} AND type = 'table'
|
|
36
|
+
SQL
|
|
37
|
+
|
|
38
|
+
table_sql.to_s.match?(/\bAUTOINCREMENT\b/i)
|
|
14
39
|
end
|
|
15
40
|
|
|
16
41
|
def prepare_column_options(column)
|
|
@@ -27,6 +27,7 @@ module ActiveRecord
|
|
|
27
27
|
col["name"]
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
where = where.sub(/\s*\/\*.*\*\/\z/, "") if where
|
|
30
31
|
orders = {}
|
|
31
32
|
|
|
32
33
|
if columns.any?(&:nil?) # index created with an expression
|
|
@@ -83,6 +84,10 @@ module ActiveRecord
|
|
|
83
84
|
alter_table(from_table, foreign_keys)
|
|
84
85
|
end
|
|
85
86
|
|
|
87
|
+
def virtual_table_exists?(table_name)
|
|
88
|
+
query_values(data_source_sql(table_name, type: "VIRTUAL TABLE"), "SCHEMA").any?
|
|
89
|
+
end
|
|
90
|
+
|
|
86
91
|
def check_constraints(table_name)
|
|
87
92
|
table_sql = query_value(<<-SQL, "SCHEMA")
|
|
88
93
|
SELECT sql
|
|
@@ -177,7 +182,8 @@ module ActiveRecord
|
|
|
177
182
|
scope = quoted_scope(name, type: type)
|
|
178
183
|
scope[:type] ||= "'table','view'"
|
|
179
184
|
|
|
180
|
-
sql = +"SELECT name FROM
|
|
185
|
+
sql = +"SELECT name FROM pragma_table_list WHERE schema <> 'temp'"
|
|
186
|
+
sql << " AND name NOT IN ('sqlite_sequence', 'sqlite_schema')"
|
|
181
187
|
sql << " AND name = #{scope[:name]}" if scope[:name]
|
|
182
188
|
sql << " AND type IN (#{scope[:type]})"
|
|
183
189
|
sql
|
|
@@ -190,6 +196,8 @@ module ActiveRecord
|
|
|
190
196
|
"'table'"
|
|
191
197
|
when "VIEW"
|
|
192
198
|
"'view'"
|
|
199
|
+
when "VIRTUAL TABLE"
|
|
200
|
+
"'virtual'"
|
|
193
201
|
end
|
|
194
202
|
scope = {}
|
|
195
203
|
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?
|
|
@@ -283,6 +291,38 @@ module ActiveRecord
|
|
|
283
291
|
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
|
284
292
|
end
|
|
285
293
|
|
|
294
|
+
VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.*)\)/i
|
|
295
|
+
|
|
296
|
+
# Returns a list of defined virtual tables
|
|
297
|
+
def virtual_tables
|
|
298
|
+
query = <<~SQL
|
|
299
|
+
SELECT name, sql FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL %';
|
|
300
|
+
SQL
|
|
301
|
+
|
|
302
|
+
exec_query(query, "SCHEMA").cast_values.each_with_object({}) do |row, memo|
|
|
303
|
+
table_name, sql = row[0], row[1]
|
|
304
|
+
_, module_name, arguments = sql.match(VIRTUAL_TABLE_REGEX).to_a
|
|
305
|
+
memo[table_name] = [module_name, arguments]
|
|
306
|
+
end.to_a
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Creates a virtual table
|
|
310
|
+
#
|
|
311
|
+
# Example:
|
|
312
|
+
# create_virtual_table :emails, :fts5, ['sender', 'title', 'body']
|
|
313
|
+
def create_virtual_table(table_name, module_name, values)
|
|
314
|
+
exec_query "CREATE VIRTUAL TABLE IF NOT EXISTS #{table_name} USING #{module_name} (#{values.join(", ")})"
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Drops a virtual table
|
|
318
|
+
#
|
|
319
|
+
# Although this command ignores +module_name+ and +values+,
|
|
320
|
+
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
|
321
|
+
# In that case, +module_name+, +values+ and +options+ will be used by #create_virtual_table.
|
|
322
|
+
def drop_virtual_table(table_name, module_name, values, **options)
|
|
323
|
+
drop_table(table_name)
|
|
324
|
+
end
|
|
325
|
+
|
|
286
326
|
# Renames a table.
|
|
287
327
|
#
|
|
288
328
|
# Example:
|
|
@@ -433,10 +473,6 @@ module ActiveRecord
|
|
|
433
473
|
@config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
|
|
434
474
|
end
|
|
435
475
|
|
|
436
|
-
def use_insert_returning?
|
|
437
|
-
@use_insert_returning
|
|
438
|
-
end
|
|
439
|
-
|
|
440
476
|
def get_database_version # :nodoc:
|
|
441
477
|
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
|
|
442
478
|
end
|
|
@@ -666,6 +702,8 @@ module ActiveRecord
|
|
|
666
702
|
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
667
703
|
elsif exception.message.match?(/called on a closed database/i)
|
|
668
704
|
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
|
705
|
+
elsif exception.is_a?(::SQLite3::BusyException)
|
|
706
|
+
StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
669
707
|
else
|
|
670
708
|
super
|
|
671
709
|
end
|
|
@@ -783,12 +821,15 @@ module ActiveRecord
|
|
|
783
821
|
if @config[:timeout] && @config[:retries]
|
|
784
822
|
raise ArgumentError, "Cannot specify both timeout and retries arguments"
|
|
785
823
|
elsif @config[:timeout]
|
|
786
|
-
|
|
824
|
+
timeout = self.class.type_cast_config_to_integer(@config[:timeout])
|
|
825
|
+
raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer)
|
|
826
|
+
@raw_connection.busy_handler_timeout = timeout
|
|
787
827
|
elsif @config[:retries]
|
|
828
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
|
829
|
+
The retries option is deprecated and will be removed in Rails 8.1. Use timeout instead.
|
|
830
|
+
MSG
|
|
788
831
|
retries = self.class.type_cast_config_to_integer(@config[:retries])
|
|
789
|
-
raw_connection.busy_handler
|
|
790
|
-
count <= retries
|
|
791
|
-
end
|
|
832
|
+
raw_connection.busy_handler { |count| count <= retries }
|
|
792
833
|
end
|
|
793
834
|
|
|
794
835
|
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(", ")}.
|