activerecord 6.0.0.beta1 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +455 -9
- data/README.rdoc +3 -1
- data/lib/active_record/associations/association.rb +18 -1
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +5 -2
- data/lib/active_record/associations/builder/collection_association.rb +5 -15
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_association.rb +5 -6
- data/lib/active_record/associations/collection_proxy.rb +13 -42
- data/lib/active_record/associations/has_many_association.rb +1 -9
- data/lib/active_record/associations/has_many_through_association.rb +4 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
- data/lib/active_record/associations/join_dependency.rb +10 -9
- data/lib/active_record/associations/preloader/association.rb +37 -34
- data/lib/active_record/associations/preloader/through_association.rb +48 -39
- data/lib/active_record/associations/preloader.rb +11 -6
- data/lib/active_record/associations.rb +3 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +47 -14
- data/lib/active_record/attribute_methods/primary_key.rb +7 -15
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +3 -9
- data/lib/active_record/attribute_methods/write.rb +6 -12
- data/lib/active_record/attribute_methods.rb +3 -53
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +15 -5
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/callbacks.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +124 -23
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +101 -70
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +11 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
- data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
- data/lib/active_record/connection_adapters/abstract_adapter.rb +108 -39
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +93 -134
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +1 -1
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +91 -24
- data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +69 -118
- data/lib/active_record/connection_handling.rb +32 -16
- data/lib/active_record/core.rb +27 -20
- data/lib/active_record/database_configurations/hash_config.rb +11 -11
- data/lib/active_record/database_configurations/url_config.rb +21 -16
- data/lib/active_record/database_configurations.rb +99 -50
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +15 -0
- data/lib/active_record/errors.rb +18 -13
- data/lib/active_record/fixtures.rb +11 -6
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +13 -1
- data/lib/active_record/internal_metadata.rb +5 -1
- data/lib/active_record/locking/optimistic.rb +3 -4
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +72 -63
- data/lib/active_record/migration.rb +62 -44
- data/lib/active_record/persistence.rb +212 -19
- data/lib/active_record/querying.rb +18 -14
- data/lib/active_record/railtie.rb +9 -1
- data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
- data/lib/active_record/railties/databases.rake +124 -25
- data/lib/active_record/reflection.rb +18 -32
- data/lib/active_record/relation/calculations.rb +40 -44
- data/lib/active_record/relation/delegation.rb +23 -31
- data/lib/active_record/relation/finder_methods.rb +13 -13
- data/lib/active_record/relation/merger.rb +11 -16
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +217 -68
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +10 -10
- data/lib/active_record/relation.rb +184 -35
- data/lib/active_record/sanitization.rb +33 -4
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +10 -1
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/scoping/default.rb +7 -15
- data/lib/active_record/scoping/named.rb +10 -2
- data/lib/active_record/scoping.rb +6 -7
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/store.rb +48 -0
- data/lib/active_record/table_metadata.rb +9 -13
- data/lib/active_record/tasks/database_tasks.rb +109 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
- data/lib/active_record/test_databases.rb +1 -16
- data/lib/active_record/test_fixtures.rb +2 -2
- data/lib/active_record/timestamp.rb +35 -19
- data/lib/active_record/touch_later.rb +4 -2
- data/lib/active_record/transactions.rb +55 -45
- data/lib/active_record/type_caster/connection.rb +16 -10
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record.rb +7 -1
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes/and.rb +1 -1
- data/lib/arel/nodes/case.rb +1 -1
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/select_core.rb +16 -12
- data/lib/arel/nodes/unary.rb +1 -0
- data/lib/arel/nodes/values_list.rb +2 -17
- data/lib/arel/nodes.rb +2 -1
- data/lib/arel/select_manager.rb +10 -10
- data/lib/arel/visitors/depth_first.rb +7 -2
- data/lib/arel/visitors/dot.rb +7 -2
- data/lib/arel/visitors/ibm_db.rb +13 -0
- data/lib/arel/visitors/informix.rb +6 -0
- data/lib/arel/visitors/mssql.rb +15 -1
- data/lib/arel/visitors/oracle12.rb +4 -5
- data/lib/arel/visitors/postgresql.rb +4 -10
- data/lib/arel/visitors/to_sql.rb +107 -131
- data/lib/arel/visitors/visitor.rb +9 -5
- data/lib/arel.rb +7 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +17 -13
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/arel/nodes/values.rb +0 -16
@@ -4,12 +4,13 @@ require "active_record/connection_adapters/abstract_adapter"
|
|
4
4
|
require "active_record/connection_adapters/statement_pool"
|
5
5
|
require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
|
6
6
|
require "active_record/connection_adapters/sqlite3/quoting"
|
7
|
+
require "active_record/connection_adapters/sqlite3/database_statements"
|
7
8
|
require "active_record/connection_adapters/sqlite3/schema_creation"
|
8
9
|
require "active_record/connection_adapters/sqlite3/schema_definitions"
|
9
10
|
require "active_record/connection_adapters/sqlite3/schema_dumper"
|
10
11
|
require "active_record/connection_adapters/sqlite3/schema_statements"
|
11
12
|
|
12
|
-
gem "sqlite3", "~> 1.
|
13
|
+
gem "sqlite3", "~> 1.4"
|
13
14
|
require "sqlite3"
|
14
15
|
|
15
16
|
module ActiveRecord
|
@@ -36,8 +37,6 @@ module ActiveRecord
|
|
36
37
|
config.merge(results_as_hash: true)
|
37
38
|
)
|
38
39
|
|
39
|
-
db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
|
40
|
-
|
41
40
|
ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
|
42
41
|
rescue Errno::ENOENT => error
|
43
42
|
if error.message.include?("No such file or directory")
|
@@ -60,6 +59,7 @@ module ActiveRecord
|
|
60
59
|
|
61
60
|
include SQLite3::Quoting
|
62
61
|
include SQLite3::SchemaStatements
|
62
|
+
include SQLite3::DatabaseStatements
|
63
63
|
|
64
64
|
NATIVE_DATABASE_TYPES = {
|
65
65
|
primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
|
@@ -95,12 +95,19 @@ module ActiveRecord
|
|
95
95
|
|
96
96
|
def initialize(connection, logger, connection_options, config)
|
97
97
|
super(connection, logger, config)
|
98
|
-
|
99
|
-
@active = true
|
100
|
-
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
|
101
98
|
configure_connection
|
102
99
|
end
|
103
100
|
|
101
|
+
def self.database_exists?(config)
|
102
|
+
config = config.symbolize_keys
|
103
|
+
if config[:database] == ":memory:"
|
104
|
+
return true
|
105
|
+
else
|
106
|
+
database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
|
107
|
+
File.exist?(database_file)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
104
111
|
def supports_ddl_transactions?
|
105
112
|
true
|
106
113
|
end
|
@@ -114,14 +121,14 @@ module ActiveRecord
|
|
114
121
|
end
|
115
122
|
|
116
123
|
def supports_expression_index?
|
117
|
-
|
124
|
+
database_version >= "3.9.0"
|
118
125
|
end
|
119
126
|
|
120
127
|
def requires_reloading?
|
121
128
|
true
|
122
129
|
end
|
123
130
|
|
124
|
-
def
|
131
|
+
def supports_foreign_keys?
|
125
132
|
true
|
126
133
|
end
|
127
134
|
|
@@ -137,23 +144,29 @@ module ActiveRecord
|
|
137
144
|
true
|
138
145
|
end
|
139
146
|
|
147
|
+
def supports_insert_on_conflict?
|
148
|
+
database_version >= "3.24.0"
|
149
|
+
end
|
150
|
+
alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
|
151
|
+
alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
|
152
|
+
alias supports_insert_conflict_target? supports_insert_on_conflict?
|
153
|
+
|
140
154
|
def active?
|
141
|
-
|
155
|
+
!@connection.closed?
|
156
|
+
end
|
157
|
+
|
158
|
+
def reconnect!
|
159
|
+
super
|
160
|
+
connect if @connection.closed?
|
142
161
|
end
|
143
162
|
|
144
163
|
# Disconnects from the database if already connected. Otherwise, this
|
145
164
|
# method does nothing.
|
146
165
|
def disconnect!
|
147
166
|
super
|
148
|
-
@active = false
|
149
167
|
@connection.close rescue nil
|
150
168
|
end
|
151
169
|
|
152
|
-
# Clears the prepared statements cache.
|
153
|
-
def clear_cache!
|
154
|
-
@statements.clear
|
155
|
-
end
|
156
|
-
|
157
170
|
def supports_index_sort_order?
|
158
171
|
true
|
159
172
|
end
|
@@ -201,91 +214,11 @@ module ActiveRecord
|
|
201
214
|
#--
|
202
215
|
# DATABASE STATEMENTS ======================================
|
203
216
|
#++
|
204
|
-
|
205
|
-
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback) # :nodoc:
|
206
|
-
private_constant :READ_QUERY
|
207
|
-
|
208
|
-
def write_query?(sql) # :nodoc:
|
209
|
-
!READ_QUERY.match?(sql)
|
210
|
-
end
|
211
|
-
|
212
217
|
def explain(arel, binds = [])
|
213
218
|
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
|
214
219
|
SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
|
215
220
|
end
|
216
221
|
|
217
|
-
def exec_query(sql, name = nil, binds = [], prepare: false)
|
218
|
-
if preventing_writes? && write_query?(sql)
|
219
|
-
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
220
|
-
end
|
221
|
-
|
222
|
-
materialize_transactions
|
223
|
-
|
224
|
-
type_casted_binds = type_casted_binds(binds)
|
225
|
-
|
226
|
-
log(sql, name, binds, type_casted_binds) do
|
227
|
-
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
228
|
-
# Don't cache statements if they are not prepared
|
229
|
-
unless prepare
|
230
|
-
stmt = @connection.prepare(sql)
|
231
|
-
begin
|
232
|
-
cols = stmt.columns
|
233
|
-
unless without_prepared_statement?(binds)
|
234
|
-
stmt.bind_params(type_casted_binds)
|
235
|
-
end
|
236
|
-
records = stmt.to_a
|
237
|
-
ensure
|
238
|
-
stmt.close
|
239
|
-
end
|
240
|
-
else
|
241
|
-
stmt = @statements[sql] ||= @connection.prepare(sql)
|
242
|
-
cols = stmt.columns
|
243
|
-
stmt.reset!
|
244
|
-
stmt.bind_params(type_casted_binds)
|
245
|
-
records = stmt.to_a
|
246
|
-
end
|
247
|
-
|
248
|
-
ActiveRecord::Result.new(cols, records)
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
def exec_delete(sql, name = "SQL", binds = [])
|
254
|
-
exec_query(sql, name, binds)
|
255
|
-
@connection.changes
|
256
|
-
end
|
257
|
-
alias :exec_update :exec_delete
|
258
|
-
|
259
|
-
def last_inserted_id(result)
|
260
|
-
@connection.last_insert_row_id
|
261
|
-
end
|
262
|
-
|
263
|
-
def execute(sql, name = nil) #:nodoc:
|
264
|
-
if preventing_writes? && write_query?(sql)
|
265
|
-
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
266
|
-
end
|
267
|
-
|
268
|
-
materialize_transactions
|
269
|
-
|
270
|
-
log(sql, name) do
|
271
|
-
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
272
|
-
@connection.execute(sql)
|
273
|
-
end
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
def begin_db_transaction #:nodoc:
|
278
|
-
log("begin transaction", nil) { @connection.transaction }
|
279
|
-
end
|
280
|
-
|
281
|
-
def commit_db_transaction #:nodoc:
|
282
|
-
log("commit transaction", nil) { @connection.commit }
|
283
|
-
end
|
284
|
-
|
285
|
-
def exec_rollback_db_transaction #:nodoc:
|
286
|
-
log("rollback transaction", nil) { @connection.rollback }
|
287
|
-
end
|
288
|
-
|
289
222
|
# SCHEMA STATEMENTS ========================================
|
290
223
|
|
291
224
|
def primary_keys(table_name) # :nodoc:
|
@@ -320,6 +253,9 @@ module ActiveRecord
|
|
320
253
|
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
|
321
254
|
alter_table(table_name) do |definition|
|
322
255
|
definition.remove_column column_name
|
256
|
+
definition.foreign_keys.delete_if do |_, fk_options|
|
257
|
+
fk_options[:column] == column_name.to_s
|
258
|
+
end
|
323
259
|
end
|
324
260
|
end
|
325
261
|
|
@@ -378,15 +314,26 @@ module ActiveRecord
|
|
378
314
|
end
|
379
315
|
end
|
380
316
|
|
381
|
-
def
|
382
|
-
|
383
|
-
transaction(requires_new: true) do
|
384
|
-
tables_to_delete.each { |table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete" }
|
317
|
+
def build_insert_sql(insert) # :nodoc:
|
318
|
+
sql = +"INSERT #{insert.into} #{insert.values_list}"
|
385
319
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
320
|
+
if insert.skip_duplicates?
|
321
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
|
322
|
+
elsif insert.update_duplicates?
|
323
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
|
324
|
+
sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
|
325
|
+
end
|
326
|
+
|
327
|
+
sql
|
328
|
+
end
|
329
|
+
|
330
|
+
def get_database_version # :nodoc:
|
331
|
+
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
|
332
|
+
end
|
333
|
+
|
334
|
+
def check_version # :nodoc:
|
335
|
+
if database_version < "3.8.0"
|
336
|
+
raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.8."
|
390
337
|
end
|
391
338
|
end
|
392
339
|
|
@@ -397,12 +344,6 @@ module ActiveRecord
|
|
397
344
|
999
|
398
345
|
end
|
399
346
|
|
400
|
-
def check_version
|
401
|
-
if sqlite_version < "3.8.0"
|
402
|
-
raise "Your version of SQLite (#{sqlite_version}) is too old. Active Record supports SQLite >= 3.8."
|
403
|
-
end
|
404
|
-
end
|
405
|
-
|
406
347
|
def initialize_type_map(m = type_map)
|
407
348
|
super
|
408
349
|
register_class_with_limit m, %r(int)i, SQLite3Integer
|
@@ -421,9 +362,8 @@ module ActiveRecord
|
|
421
362
|
type.to_sym == :primary_key || options[:primary_key]
|
422
363
|
end
|
423
364
|
|
424
|
-
def alter_table(table_name,
|
365
|
+
def alter_table(table_name, foreign_keys = foreign_keys(table_name), **options)
|
425
366
|
altered_table_name = "a#{table_name}"
|
426
|
-
foreign_keys = foreign_keys(table_name)
|
427
367
|
|
428
368
|
caller = lambda do |definition|
|
429
369
|
rename = options[:rename] || {}
|
@@ -431,7 +371,8 @@ module ActiveRecord
|
|
431
371
|
if column = rename[fk.options[:column]]
|
432
372
|
fk.options[:column] = column
|
433
373
|
end
|
434
|
-
|
374
|
+
to_table = strip_table_name_prefix_and_suffix(fk.to_table)
|
375
|
+
definition.foreign_key(to_table, fk.options)
|
435
376
|
end
|
436
377
|
|
437
378
|
yield definition if block_given?
|
@@ -520,10 +461,6 @@ module ActiveRecord
|
|
520
461
|
SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
|
521
462
|
end
|
522
463
|
|
523
|
-
def sqlite_version
|
524
|
-
@sqlite_version ||= SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
|
525
|
-
end
|
526
|
-
|
527
464
|
def translate_exception(exception, message:, sql:, binds:)
|
528
465
|
case exception.message
|
529
466
|
# SQLite 3.8.2 returns a newly formatted error message:
|
@@ -558,9 +495,9 @@ module ActiveRecord
|
|
558
495
|
result = exec_query(sql, "SCHEMA").first
|
559
496
|
|
560
497
|
if result
|
561
|
-
# Splitting with left parentheses and
|
498
|
+
# Splitting with left parentheses and discarding the first part will return all
|
562
499
|
# columns separated with comma(,).
|
563
|
-
columns_string = result["sql"].split("(").last
|
500
|
+
columns_string = result["sql"].split("(", 2).last
|
564
501
|
|
565
502
|
columns_string.split(",").each do |column_string|
|
566
503
|
# This regex will match the column name and collation type and will save
|
@@ -586,7 +523,21 @@ module ActiveRecord
|
|
586
523
|
Arel::Visitors::SQLite.new(self)
|
587
524
|
end
|
588
525
|
|
526
|
+
def build_statement_pool
|
527
|
+
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
528
|
+
end
|
529
|
+
|
530
|
+
def connect
|
531
|
+
@connection = ::SQLite3::Database.new(
|
532
|
+
@config[:database].to_s,
|
533
|
+
@config.merge(results_as_hash: true)
|
534
|
+
)
|
535
|
+
configure_connection
|
536
|
+
end
|
537
|
+
|
589
538
|
def configure_connection
|
539
|
+
@connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
|
540
|
+
|
590
541
|
execute("PRAGMA foreign_keys = ON", "SCHEMA")
|
591
542
|
end
|
592
543
|
|
@@ -85,14 +85,14 @@ module ActiveRecord
|
|
85
85
|
# based on the requested role:
|
86
86
|
#
|
87
87
|
# ActiveRecord::Base.connected_to(role: :writing) do
|
88
|
-
# Dog.create! # creates dog using dog connection
|
88
|
+
# Dog.create! # creates dog using dog writing connection
|
89
89
|
# end
|
90
90
|
#
|
91
91
|
# ActiveRecord::Base.connected_to(role: :reading) do
|
92
92
|
# Dog.create! # throws exception because we're on a replica
|
93
93
|
# end
|
94
94
|
#
|
95
|
-
# ActiveRecord::Base.connected_to(role: :
|
95
|
+
# ActiveRecord::Base.connected_to(role: :unknown_role) do
|
96
96
|
# # raises exception due to non-existent role
|
97
97
|
# end
|
98
98
|
#
|
@@ -100,11 +100,20 @@ module ActiveRecord
|
|
100
100
|
# you can use +connected_to+ with a +database+ argument. The +database+ argument
|
101
101
|
# expects a symbol that corresponds to the database key in your config.
|
102
102
|
#
|
103
|
-
# This will connect to a new database for the queries inside the block.
|
104
|
-
#
|
105
103
|
# ActiveRecord::Base.connected_to(database: :animals_slow_replica) do
|
106
104
|
# Dog.run_a_long_query # runs a long query while connected to the +animals_slow_replica+
|
107
105
|
# end
|
106
|
+
#
|
107
|
+
# This will connect to a new database for the queries inside the block. By
|
108
|
+
# default the `:writing` role will be used since all connections must be assigned
|
109
|
+
# a role. If you would like to use a different role you can pass a hash to database:
|
110
|
+
#
|
111
|
+
# ActiveRecord::Base.connected_to(database: { readonly_slow: :animals_slow_replica }) do
|
112
|
+
# # runs a long query while connected to the +animals_slow_replica+ using the readonly_slow role.
|
113
|
+
# Dog.run_a_long_query
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# When using the database key a new connection will be established every time.
|
108
117
|
def connected_to(database: nil, role: nil, &blk)
|
109
118
|
if database && role
|
110
119
|
raise ArgumentError, "connected_to can only accept a `database` or a `role` argument, but not both arguments."
|
@@ -112,17 +121,14 @@ module ActiveRecord
|
|
112
121
|
if database.is_a?(Hash)
|
113
122
|
role, database = database.first
|
114
123
|
role = role.to_sym
|
115
|
-
else
|
116
|
-
role = database.to_sym
|
117
124
|
end
|
118
125
|
|
119
126
|
config_hash = resolve_config_for_connection(database)
|
120
127
|
handler = lookup_connection_handler(role)
|
121
128
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
end
|
129
|
+
handler.establish_connection(config_hash)
|
130
|
+
|
131
|
+
with_handler(role, &blk)
|
126
132
|
elsif role
|
127
133
|
with_handler(role.to_sym, &blk)
|
128
134
|
else
|
@@ -154,14 +160,11 @@ module ActiveRecord
|
|
154
160
|
end
|
155
161
|
|
156
162
|
def lookup_connection_handler(handler_key) # :nodoc:
|
163
|
+
handler_key ||= ActiveRecord::Base.writing_role
|
157
164
|
connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
|
158
165
|
end
|
159
166
|
|
160
167
|
def with_handler(handler_key, &blk) # :nodoc:
|
161
|
-
unless ActiveRecord::Base.connection_handlers.keys.include?(handler_key)
|
162
|
-
raise ArgumentError, "The #{handler_key} role does not exist. Add it by establishing a connection with `connects_to` or use an existing role (#{ActiveRecord::Base.connection_handlers.keys.join(", ")})."
|
163
|
-
end
|
164
|
-
|
165
168
|
handler = lookup_connection_handler(handler_key)
|
166
169
|
swap_connection_handler(handler, &blk)
|
167
170
|
end
|
@@ -170,7 +173,7 @@ module ActiveRecord
|
|
170
173
|
raise "Anonymous class is not allowed." unless name
|
171
174
|
|
172
175
|
config_or_env ||= DEFAULT_ENV.call.to_sym
|
173
|
-
pool_name =
|
176
|
+
pool_name = primary_class? ? "primary" : name
|
174
177
|
self.connection_specification_name = pool_name
|
175
178
|
|
176
179
|
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
|
@@ -180,6 +183,15 @@ module ActiveRecord
|
|
180
183
|
config_hash
|
181
184
|
end
|
182
185
|
|
186
|
+
# Clears the query cache for all connections associated with the current thread.
|
187
|
+
def clear_query_caches_for_current_thread
|
188
|
+
ActiveRecord::Base.connection_handlers.each_value do |handler|
|
189
|
+
handler.connection_pool_list.each do |pool|
|
190
|
+
pool.connection.clear_query_cache if pool.active_connection?
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
183
195
|
# Returns the connection currently associated with the class. This can
|
184
196
|
# also be used to "borrow" the connection to do database work unrelated
|
185
197
|
# to any of the specific Active Records.
|
@@ -192,11 +204,15 @@ module ActiveRecord
|
|
192
204
|
# Return the specification name from the current class or its parent.
|
193
205
|
def connection_specification_name
|
194
206
|
if !defined?(@connection_specification_name) || @connection_specification_name.nil?
|
195
|
-
return
|
207
|
+
return primary_class? ? "primary" : superclass.connection_specification_name
|
196
208
|
end
|
197
209
|
@connection_specification_name
|
198
210
|
end
|
199
211
|
|
212
|
+
def primary_class? # :nodoc:
|
213
|
+
self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
|
214
|
+
end
|
215
|
+
|
200
216
|
# Returns the configuration of the associated connection as a hash:
|
201
217
|
#
|
202
218
|
# ActiveRecord::Base.connection_config
|
data/lib/active_record/core.rb
CHANGED
@@ -124,6 +124,10 @@ module ActiveRecord
|
|
124
124
|
|
125
125
|
mattr_accessor :connection_handlers, instance_accessor: false, default: {}
|
126
126
|
|
127
|
+
mattr_accessor :writing_role, instance_accessor: false, default: :writing
|
128
|
+
|
129
|
+
mattr_accessor :reading_role, instance_accessor: false, default: :reading
|
130
|
+
|
127
131
|
class_attribute :default_connection_handler, instance_writer: false
|
128
132
|
|
129
133
|
self.filter_attributes = []
|
@@ -137,7 +141,6 @@ module ActiveRecord
|
|
137
141
|
end
|
138
142
|
|
139
143
|
self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
|
140
|
-
self.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
|
141
144
|
end
|
142
145
|
|
143
146
|
module ClassMethods
|
@@ -157,7 +160,7 @@ module ActiveRecord
|
|
157
160
|
return super if block_given? ||
|
158
161
|
primary_key.nil? ||
|
159
162
|
scope_attributes? ||
|
160
|
-
columns_hash.
|
163
|
+
columns_hash.key?(inheritance_column) && !base_class?
|
161
164
|
|
162
165
|
id = ids.first
|
163
166
|
|
@@ -171,14 +174,14 @@ module ActiveRecord
|
|
171
174
|
|
172
175
|
record = statement.execute([id], connection)&.first
|
173
176
|
unless record
|
174
|
-
raise RecordNotFound.new("Couldn't find #{name} with '#{
|
175
|
-
name, primary_key, id)
|
177
|
+
raise RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
|
176
178
|
end
|
177
179
|
record
|
178
180
|
end
|
179
181
|
|
180
182
|
def find_by(*args) # :nodoc:
|
181
|
-
return super if scope_attributes? || reflect_on_all_aggregations.any?
|
183
|
+
return super if scope_attributes? || reflect_on_all_aggregations.any? ||
|
184
|
+
columns_hash.key?(inheritance_column) && !base_class?
|
182
185
|
|
183
186
|
hash = args.first
|
184
187
|
|
@@ -265,7 +268,8 @@ module ActiveRecord
|
|
265
268
|
end
|
266
269
|
|
267
270
|
def arel_attribute(name, table = arel_table) # :nodoc:
|
268
|
-
name =
|
271
|
+
name = name.to_s
|
272
|
+
name = attribute_aliases[name] || name
|
269
273
|
table[name]
|
270
274
|
end
|
271
275
|
|
@@ -313,7 +317,7 @@ module ActiveRecord
|
|
313
317
|
# # Instantiates a single new object
|
314
318
|
# User.new(first_name: 'Jamie')
|
315
319
|
def initialize(attributes = nil)
|
316
|
-
|
320
|
+
@new_record = true
|
317
321
|
@attributes = self.class._default_attributes.deep_dup
|
318
322
|
|
319
323
|
init_internals
|
@@ -350,12 +354,10 @@ module ActiveRecord
|
|
350
354
|
# +attributes+ should be an attributes object, and unlike the
|
351
355
|
# `initialize` method, no assignment calls are made per attribute.
|
352
356
|
def init_with_attributes(attributes, new_record = false) # :nodoc:
|
353
|
-
init_internals
|
354
|
-
|
355
357
|
@new_record = new_record
|
356
358
|
@attributes = attributes
|
357
359
|
|
358
|
-
|
360
|
+
init_internals
|
359
361
|
|
360
362
|
yield self if block_given?
|
361
363
|
|
@@ -394,13 +396,13 @@ module ActiveRecord
|
|
394
396
|
##
|
395
397
|
def initialize_dup(other) # :nodoc:
|
396
398
|
@attributes = @attributes.deep_dup
|
397
|
-
@attributes.reset(
|
399
|
+
@attributes.reset(@primary_key)
|
398
400
|
|
399
401
|
_run_initialize_callbacks
|
400
402
|
|
401
403
|
@new_record = true
|
402
404
|
@destroyed = false
|
403
|
-
@_start_transaction_state =
|
405
|
+
@_start_transaction_state = nil
|
404
406
|
@transaction_state = nil
|
405
407
|
|
406
408
|
super
|
@@ -461,6 +463,7 @@ module ActiveRecord
|
|
461
463
|
|
462
464
|
# Returns +true+ if the attributes hash has been frozen.
|
463
465
|
def frozen?
|
466
|
+
sync_with_transaction_state if @transaction_state&.finalized?
|
464
467
|
@attributes.frozen?
|
465
468
|
end
|
466
469
|
|
@@ -473,6 +476,14 @@ module ActiveRecord
|
|
473
476
|
end
|
474
477
|
end
|
475
478
|
|
479
|
+
def present? # :nodoc:
|
480
|
+
true
|
481
|
+
end
|
482
|
+
|
483
|
+
def blank? # :nodoc:
|
484
|
+
false
|
485
|
+
end
|
486
|
+
|
476
487
|
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
|
477
488
|
# attributes will be marked as read only since they cannot be saved.
|
478
489
|
def readonly?
|
@@ -557,22 +568,18 @@ module ActiveRecord
|
|
557
568
|
end
|
558
569
|
|
559
570
|
def init_internals
|
571
|
+
@primary_key = self.class.primary_key
|
560
572
|
@readonly = false
|
561
573
|
@destroyed = false
|
562
574
|
@marked_for_destruction = false
|
563
575
|
@destroyed_by_association = nil
|
564
|
-
@
|
565
|
-
@_start_transaction_state = {}
|
576
|
+
@_start_transaction_state = nil
|
566
577
|
@transaction_state = nil
|
567
|
-
end
|
568
578
|
|
569
|
-
|
579
|
+
self.class.define_attribute_methods
|
570
580
|
end
|
571
581
|
|
572
|
-
def
|
573
|
-
if frozen?
|
574
|
-
@attributes = @attributes.dup
|
575
|
-
end
|
582
|
+
def initialize_internals_callback
|
576
583
|
end
|
577
584
|
|
578
585
|
def custom_inspect_method_defined?
|
@@ -14,16 +14,16 @@ module ActiveRecord
|
|
14
14
|
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
|
15
15
|
# @env_name="development", @spec_name="primary", @config={"database"=>"db_name"}>
|
16
16
|
#
|
17
|
-
# Options
|
17
|
+
# ==== Options
|
18
18
|
#
|
19
|
-
# <tt>:env_name</tt> - The Rails environment,
|
20
|
-
# <tt>:spec_name</tt> - The specification name. In a standard two-tier
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
# <tt>:config</tt> - The config hash. This is the hash that contains the
|
25
|
-
#
|
26
|
-
#
|
19
|
+
# * <tt>:env_name</tt> - The Rails environment, i.e. "development".
|
20
|
+
# * <tt>:spec_name</tt> - The specification name. In a standard two-tier
|
21
|
+
# database configuration this will default to "primary". In a multiple
|
22
|
+
# database three-tier database configuration this corresponds to the name
|
23
|
+
# used in the second tier, for example "primary_readonly".
|
24
|
+
# * <tt>:config</tt> - The config hash. This is the hash that contains the
|
25
|
+
# database adapter, name, and other important information for database
|
26
|
+
# connections.
|
27
27
|
class HashConfig < DatabaseConfig
|
28
28
|
attr_reader :config
|
29
29
|
|
@@ -33,14 +33,14 @@ module ActiveRecord
|
|
33
33
|
end
|
34
34
|
|
35
35
|
# Determines whether a database configuration is for a replica / readonly
|
36
|
-
# connection. If the
|
36
|
+
# connection. If the +replica+ key is present in the config, +replica?+ will
|
37
37
|
# return +true+.
|
38
38
|
def replica?
|
39
39
|
config["replica"]
|
40
40
|
end
|
41
41
|
|
42
42
|
# The migrations paths for a database configuration. If the
|
43
|
-
#
|
43
|
+
# +migrations_paths+ key is present in the config, +migrations_paths+
|
44
44
|
# will return its value.
|
45
45
|
def migrations_paths
|
46
46
|
config["migrations_paths"]
|
@@ -17,17 +17,17 @@ module ActiveRecord
|
|
17
17
|
# @config={"adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost"},
|
18
18
|
# @url="postgres://localhost/foo">
|
19
19
|
#
|
20
|
-
# Options
|
20
|
+
# ==== Options
|
21
21
|
#
|
22
|
-
# <tt>:env_name</tt> - The Rails environment, ie "development"
|
23
|
-
# <tt>:spec_name</tt> - The specification name. In a standard two-tier
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# <tt>:url</tt> - The database URL.
|
28
|
-
# <tt>:config</tt> - The config hash. This is the hash that contains the
|
29
|
-
#
|
30
|
-
#
|
22
|
+
# * <tt>:env_name</tt> - The Rails environment, ie "development".
|
23
|
+
# * <tt>:spec_name</tt> - The specification name. In a standard two-tier
|
24
|
+
# database configuration this will default to "primary". In a multiple
|
25
|
+
# database three-tier database configuration this corresponds to the name
|
26
|
+
# used in the second tier, for example "primary_readonly".
|
27
|
+
# * <tt>:url</tt> - The database URL.
|
28
|
+
# * <tt>:config</tt> - The config hash. This is the hash that contains the
|
29
|
+
# database adapter, name, and other important information for database
|
30
|
+
# connections.
|
31
31
|
class UrlConfig < DatabaseConfig
|
32
32
|
attr_reader :url, :config
|
33
33
|
|
@@ -42,26 +42,31 @@ module ActiveRecord
|
|
42
42
|
end
|
43
43
|
|
44
44
|
# Determines whether a database configuration is for a replica / readonly
|
45
|
-
# connection. If the
|
45
|
+
# connection. If the +replica+ key is present in the config, +replica?+ will
|
46
46
|
# return +true+.
|
47
47
|
def replica?
|
48
48
|
config["replica"]
|
49
49
|
end
|
50
50
|
|
51
51
|
# The migrations paths for a database configuration. If the
|
52
|
-
#
|
52
|
+
# +migrations_paths+ key is present in the config, +migrations_paths+
|
53
53
|
# will return its value.
|
54
54
|
def migrations_paths
|
55
55
|
config["migrations_paths"]
|
56
56
|
end
|
57
57
|
|
58
58
|
private
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
|
60
|
+
def build_url_hash(url)
|
61
|
+
if url.nil? || /^jdbc:/.match?(url)
|
62
|
+
{ "url" => url }
|
62
63
|
else
|
63
|
-
|
64
|
+
ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(url).to_hash
|
64
65
|
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_config(original_config, url)
|
69
|
+
hash = build_url_hash(url)
|
65
70
|
|
66
71
|
if original_config[env_name]
|
67
72
|
original_config[env_name].merge(hash)
|