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
@@ -3,38 +3,34 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
# :stopdoc:
|
5
5
|
module ConnectionAdapters
|
6
|
-
|
7
|
-
|
6
|
+
module PostgreSQL
|
7
|
+
class TypeMetadata < DelegateClass(SqlTypeMetadata)
|
8
|
+
undef to_yaml if method_defined?(:to_yaml)
|
8
9
|
|
9
|
-
|
10
|
+
attr_reader :oid, :fmod
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@array = /\[\]$/.match?(type_metadata.sql_type)
|
17
|
-
end
|
18
|
-
|
19
|
-
def sql_type
|
20
|
-
super.gsub(/\[\]$/, "")
|
21
|
-
end
|
22
|
-
|
23
|
-
def ==(other)
|
24
|
-
other.is_a?(PostgreSQLTypeMetadata) &&
|
25
|
-
attributes_for_hash == other.attributes_for_hash
|
26
|
-
end
|
27
|
-
alias eql? ==
|
28
|
-
|
29
|
-
def hash
|
30
|
-
attributes_for_hash.hash
|
31
|
-
end
|
12
|
+
def initialize(type_metadata, oid: nil, fmod: nil)
|
13
|
+
super(type_metadata)
|
14
|
+
@oid = oid
|
15
|
+
@fmod = fmod
|
16
|
+
end
|
32
17
|
|
33
|
-
|
18
|
+
def ==(other)
|
19
|
+
other.is_a?(TypeMetadata) &&
|
20
|
+
__getobj__ == other.__getobj__ &&
|
21
|
+
oid == other.oid &&
|
22
|
+
fmod == other.fmod
|
23
|
+
end
|
24
|
+
alias eql? ==
|
34
25
|
|
35
|
-
def
|
36
|
-
|
26
|
+
def hash
|
27
|
+
TypeMetadata.hash ^
|
28
|
+
__getobj__.hash ^
|
29
|
+
oid.hash ^
|
30
|
+
fmod.hash
|
37
31
|
end
|
32
|
+
end
|
38
33
|
end
|
34
|
+
PostgreSQLTypeMetadata = PostgreSQL::TypeMetadata
|
39
35
|
end
|
40
36
|
end
|
@@ -46,7 +46,7 @@ module ActiveRecord
|
|
46
46
|
conn = PG.connect(conn_params)
|
47
47
|
ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config)
|
48
48
|
rescue ::PG::Error => error
|
49
|
-
if error.message.include?(
|
49
|
+
if error.message.include?(conn_params[:dbname])
|
50
50
|
raise ActiveRecord::NoDatabaseError
|
51
51
|
else
|
52
52
|
raise
|
@@ -196,6 +196,17 @@ module ActiveRecord
|
|
196
196
|
true
|
197
197
|
end
|
198
198
|
|
199
|
+
def supports_insert_returning?
|
200
|
+
true
|
201
|
+
end
|
202
|
+
|
203
|
+
def supports_insert_on_conflict?
|
204
|
+
database_version >= 90500
|
205
|
+
end
|
206
|
+
alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
|
207
|
+
alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
|
208
|
+
alias supports_insert_conflict_target? supports_insert_on_conflict?
|
209
|
+
|
199
210
|
def index_algorithms
|
200
211
|
{ concurrently: "CONCURRENTLY" }
|
201
212
|
end
|
@@ -240,9 +251,6 @@ module ActiveRecord
|
|
240
251
|
|
241
252
|
configure_connection
|
242
253
|
add_pg_encoders
|
243
|
-
@statements = StatementPool.new @connection,
|
244
|
-
self.class.type_cast_config_to_integer(config[:statement_limit])
|
245
|
-
|
246
254
|
add_pg_decoders
|
247
255
|
|
248
256
|
@type_map = Type::HashLookupTypeMap.new
|
@@ -251,15 +259,10 @@ module ActiveRecord
|
|
251
259
|
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
|
252
260
|
end
|
253
261
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
def truncate(table_name, name = nil)
|
262
|
-
exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, []
|
262
|
+
def self.database_exists?(config)
|
263
|
+
!!ActiveRecord::Base.postgresql_connection(config)
|
264
|
+
rescue ActiveRecord::NoDatabaseError
|
265
|
+
false
|
263
266
|
end
|
264
267
|
|
265
268
|
# Is this connection alive and ready for queries?
|
@@ -278,6 +281,8 @@ module ActiveRecord
|
|
278
281
|
super
|
279
282
|
@connection.reset
|
280
283
|
configure_connection
|
284
|
+
rescue PG::ConnectionBad
|
285
|
+
connect
|
281
286
|
end
|
282
287
|
end
|
283
288
|
|
@@ -303,6 +308,7 @@ module ActiveRecord
|
|
303
308
|
end
|
304
309
|
|
305
310
|
def discard! # :nodoc:
|
311
|
+
super
|
306
312
|
@connection.socket_io.reopen(IO::NULL) rescue nil
|
307
313
|
@connection = nil
|
308
314
|
end
|
@@ -345,7 +351,14 @@ module ActiveRecord
|
|
345
351
|
end
|
346
352
|
|
347
353
|
def supports_pgcrypto_uuid?
|
348
|
-
|
354
|
+
database_version >= 90400
|
355
|
+
end
|
356
|
+
|
357
|
+
def supports_optimizer_hints?
|
358
|
+
unless defined?(@has_pg_hint_plan)
|
359
|
+
@has_pg_hint_plan = extension_available?("pg_hint_plan")
|
360
|
+
end
|
361
|
+
@has_pg_hint_plan
|
349
362
|
end
|
350
363
|
|
351
364
|
def supports_lazy_transactions?
|
@@ -378,9 +391,12 @@ module ActiveRecord
|
|
378
391
|
}
|
379
392
|
end
|
380
393
|
|
394
|
+
def extension_available?(name)
|
395
|
+
query_value("SELECT true FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
|
396
|
+
end
|
397
|
+
|
381
398
|
def extension_enabled?(name)
|
382
|
-
|
383
|
-
res.cast_values.first
|
399
|
+
query_value("SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
|
384
400
|
end
|
385
401
|
|
386
402
|
def extensions
|
@@ -391,8 +407,6 @@ module ActiveRecord
|
|
391
407
|
def max_identifier_length
|
392
408
|
@max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
|
393
409
|
end
|
394
|
-
alias table_alias_length max_identifier_length
|
395
|
-
alias index_name_length max_identifier_length
|
396
410
|
|
397
411
|
# Set the authorized user for this session
|
398
412
|
def session_auth=(user)
|
@@ -415,21 +429,37 @@ module ActiveRecord
|
|
415
429
|
}
|
416
430
|
|
417
431
|
# Returns the version of the connected PostgreSQL server.
|
418
|
-
def
|
432
|
+
def get_database_version # :nodoc:
|
419
433
|
@connection.server_version
|
420
434
|
end
|
435
|
+
alias :postgresql_version :database_version
|
421
436
|
|
422
437
|
def default_index_type?(index) # :nodoc:
|
423
438
|
index.using == :btree || super
|
424
439
|
end
|
425
440
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
441
|
+
def build_insert_sql(insert) # :nodoc:
|
442
|
+
sql = +"INSERT #{insert.into} #{insert.values_list}"
|
443
|
+
|
444
|
+
if insert.skip_duplicates?
|
445
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
|
446
|
+
elsif insert.update_duplicates?
|
447
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
|
448
|
+
sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
|
431
449
|
end
|
432
450
|
|
451
|
+
sql << " RETURNING #{insert.returning}" if insert.returning
|
452
|
+
sql
|
453
|
+
end
|
454
|
+
|
455
|
+
def check_version # :nodoc:
|
456
|
+
if database_version < 90300
|
457
|
+
raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
private
|
462
|
+
|
433
463
|
# See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
|
434
464
|
VALUE_LIMIT_VIOLATION = "22001"
|
435
465
|
NUMERIC_VALUE_OUT_OF_RANGE = "22003"
|
@@ -628,6 +658,10 @@ module ActiveRecord
|
|
628
658
|
def exec_no_cache(sql, name, binds)
|
629
659
|
materialize_transactions
|
630
660
|
|
661
|
+
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
|
662
|
+
# made since we established the connection
|
663
|
+
update_typemap_for_default_timezone
|
664
|
+
|
631
665
|
type_casted_binds = type_casted_binds(binds)
|
632
666
|
log(sql, name, binds, type_casted_binds) do
|
633
667
|
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
@@ -638,6 +672,7 @@ module ActiveRecord
|
|
638
672
|
|
639
673
|
def exec_cache(sql, name, binds)
|
640
674
|
materialize_transactions
|
675
|
+
update_typemap_for_default_timezone
|
641
676
|
|
642
677
|
stmt_key = prepare_statement(sql, binds)
|
643
678
|
type_casted_binds = type_casted_binds(binds)
|
@@ -716,6 +751,8 @@ module ActiveRecord
|
|
716
751
|
def connect
|
717
752
|
@connection = PG.connect(@connection_parameters)
|
718
753
|
configure_connection
|
754
|
+
add_pg_encoders
|
755
|
+
add_pg_decoders
|
719
756
|
end
|
720
757
|
|
721
758
|
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
@@ -796,6 +833,10 @@ module ActiveRecord
|
|
796
833
|
Arel::Visitors::PostgreSQL.new(self)
|
797
834
|
end
|
798
835
|
|
836
|
+
def build_statement_pool
|
837
|
+
StatementPool.new(@connection, self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
838
|
+
end
|
839
|
+
|
799
840
|
def can_perform_case_insensitive_comparison_for?(column)
|
800
841
|
@case_insensitive_cache ||= {}
|
801
842
|
@case_insensitive_cache[column.sql_type] ||= begin
|
@@ -826,7 +867,22 @@ module ActiveRecord
|
|
826
867
|
@connection.type_map_for_queries = map
|
827
868
|
end
|
828
869
|
|
870
|
+
def update_typemap_for_default_timezone
|
871
|
+
if @default_timezone != ActiveRecord::Base.default_timezone && @timestamp_decoder
|
872
|
+
decoder_class = ActiveRecord::Base.default_timezone == :utc ?
|
873
|
+
PG::TextDecoder::TimestampUtc :
|
874
|
+
PG::TextDecoder::TimestampWithoutTimeZone
|
875
|
+
|
876
|
+
@timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
|
877
|
+
@connection.type_map_for_results.add_coder(@timestamp_decoder)
|
878
|
+
@default_timezone = ActiveRecord::Base.default_timezone
|
879
|
+
end
|
880
|
+
end
|
881
|
+
|
829
882
|
def add_pg_decoders
|
883
|
+
@default_timezone = nil
|
884
|
+
@timestamp_decoder = nil
|
885
|
+
|
830
886
|
coders_by_name = {
|
831
887
|
"int2" => PG::TextDecoder::Integer,
|
832
888
|
"int4" => PG::TextDecoder::Integer,
|
@@ -836,6 +892,13 @@ module ActiveRecord
|
|
836
892
|
"float8" => PG::TextDecoder::Float,
|
837
893
|
"bool" => PG::TextDecoder::Boolean,
|
838
894
|
}
|
895
|
+
|
896
|
+
if defined?(PG::TextDecoder::TimestampUtc)
|
897
|
+
# Use native PG encoders available since pg-1.1
|
898
|
+
coders_by_name["timestamp"] = PG::TextDecoder::TimestampUtc
|
899
|
+
coders_by_name["timestamptz"] = PG::TextDecoder::TimestampWithTimeZone
|
900
|
+
end
|
901
|
+
|
839
902
|
known_coder_types = coders_by_name.keys.map { |n| quote(n) }
|
840
903
|
query = <<~SQL % known_coder_types.join(", ")
|
841
904
|
SELECT t.oid, t.typname
|
@@ -851,6 +914,10 @@ module ActiveRecord
|
|
851
914
|
map = PG::TypeMapByOid.new
|
852
915
|
coders.each { |coder| map.add_coder(coder) }
|
853
916
|
@connection.type_map_for_results = map
|
917
|
+
|
918
|
+
# extract timestamp decoder for use in update_typemap_for_default_timezone
|
919
|
+
@timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
|
920
|
+
update_typemap_for_default_timezone
|
854
921
|
end
|
855
922
|
|
856
923
|
def construct_coder(row, coder_class)
|
@@ -13,6 +13,7 @@ module ActiveRecord
|
|
13
13
|
@columns_hash = {}
|
14
14
|
@primary_keys = {}
|
15
15
|
@data_sources = {}
|
16
|
+
@indexes = {}
|
16
17
|
end
|
17
18
|
|
18
19
|
def initialize_dup(other)
|
@@ -21,22 +22,27 @@ module ActiveRecord
|
|
21
22
|
@columns_hash = @columns_hash.dup
|
22
23
|
@primary_keys = @primary_keys.dup
|
23
24
|
@data_sources = @data_sources.dup
|
25
|
+
@indexes = @indexes.dup
|
24
26
|
end
|
25
27
|
|
26
28
|
def encode_with(coder)
|
27
|
-
coder["columns"]
|
28
|
-
coder["columns_hash"]
|
29
|
-
coder["primary_keys"]
|
30
|
-
coder["data_sources"]
|
31
|
-
coder["
|
29
|
+
coder["columns"] = @columns
|
30
|
+
coder["columns_hash"] = @columns_hash
|
31
|
+
coder["primary_keys"] = @primary_keys
|
32
|
+
coder["data_sources"] = @data_sources
|
33
|
+
coder["indexes"] = @indexes
|
34
|
+
coder["version"] = connection.migration_context.current_version
|
35
|
+
coder["database_version"] = database_version
|
32
36
|
end
|
33
37
|
|
34
38
|
def init_with(coder)
|
35
|
-
@columns
|
36
|
-
@columns_hash
|
37
|
-
@primary_keys
|
38
|
-
@data_sources
|
39
|
-
@
|
39
|
+
@columns = coder["columns"]
|
40
|
+
@columns_hash = coder["columns_hash"]
|
41
|
+
@primary_keys = coder["primary_keys"]
|
42
|
+
@data_sources = coder["data_sources"]
|
43
|
+
@indexes = coder["indexes"] || {}
|
44
|
+
@version = coder["version"]
|
45
|
+
@database_version = coder["database_version"]
|
40
46
|
end
|
41
47
|
|
42
48
|
def primary_keys(table_name)
|
@@ -57,6 +63,7 @@ module ActiveRecord
|
|
57
63
|
primary_keys(table_name)
|
58
64
|
columns(table_name)
|
59
65
|
columns_hash(table_name)
|
66
|
+
indexes(table_name)
|
60
67
|
end
|
61
68
|
end
|
62
69
|
|
@@ -82,17 +89,27 @@ module ActiveRecord
|
|
82
89
|
@columns_hash.key?(table_name)
|
83
90
|
end
|
84
91
|
|
92
|
+
def indexes(table_name)
|
93
|
+
@indexes[table_name] ||= connection.indexes(table_name)
|
94
|
+
end
|
95
|
+
|
96
|
+
def database_version # :nodoc:
|
97
|
+
@database_version ||= connection.get_database_version
|
98
|
+
end
|
99
|
+
|
85
100
|
# Clears out internal caches
|
86
101
|
def clear!
|
87
102
|
@columns.clear
|
88
103
|
@columns_hash.clear
|
89
104
|
@primary_keys.clear
|
90
105
|
@data_sources.clear
|
106
|
+
@indexes.clear
|
91
107
|
@version = nil
|
108
|
+
@database_version = nil
|
92
109
|
end
|
93
110
|
|
94
111
|
def size
|
95
|
-
[@columns, @columns_hash, @primary_keys, @data_sources].
|
112
|
+
[@columns, @columns_hash, @primary_keys, @data_sources].sum(&:size)
|
96
113
|
end
|
97
114
|
|
98
115
|
# Clear out internal caches for the data source +name+.
|
@@ -101,20 +118,21 @@ module ActiveRecord
|
|
101
118
|
@columns_hash.delete name
|
102
119
|
@primary_keys.delete name
|
103
120
|
@data_sources.delete name
|
121
|
+
@indexes.delete name
|
104
122
|
end
|
105
123
|
|
106
124
|
def marshal_dump
|
107
125
|
# if we get current version during initialization, it happens stack over flow.
|
108
126
|
@version = connection.migration_context.current_version
|
109
|
-
[@version, @columns, @columns_hash, @primary_keys, @data_sources]
|
127
|
+
[@version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, database_version]
|
110
128
|
end
|
111
129
|
|
112
130
|
def marshal_load(array)
|
113
|
-
@version, @columns, @columns_hash, @primary_keys, @data_sources = array
|
131
|
+
@version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
|
132
|
+
@indexes = @indexes || {}
|
114
133
|
end
|
115
134
|
|
116
135
|
private
|
117
|
-
|
118
136
|
def prepare_data_sources
|
119
137
|
connection.data_sources.each { |source| @data_sources[source] = true }
|
120
138
|
end
|
@@ -16,19 +16,22 @@ module ActiveRecord
|
|
16
16
|
|
17
17
|
def ==(other)
|
18
18
|
other.is_a?(SqlTypeMetadata) &&
|
19
|
-
|
19
|
+
sql_type == other.sql_type &&
|
20
|
+
type == other.type &&
|
21
|
+
limit == other.limit &&
|
22
|
+
precision == other.precision &&
|
23
|
+
scale == other.scale
|
20
24
|
end
|
21
25
|
alias eql? ==
|
22
26
|
|
23
27
|
def hash
|
24
|
-
|
28
|
+
SqlTypeMetadata.hash ^
|
29
|
+
sql_type.hash ^
|
30
|
+
type.hash ^
|
31
|
+
limit.hash ^
|
32
|
+
precision.hash >> 1 ^
|
33
|
+
scale.hash >> 2
|
25
34
|
end
|
26
|
-
|
27
|
-
protected
|
28
|
-
|
29
|
-
def attributes_for_hash
|
30
|
-
[self.class, sql_type, type, limit, precision, scale]
|
31
|
-
end
|
32
35
|
end
|
33
36
|
end
|
34
37
|
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module SQLite3
|
6
|
+
module DatabaseStatements
|
7
|
+
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback) # :nodoc:
|
8
|
+
private_constant :READ_QUERY
|
9
|
+
|
10
|
+
def write_query?(sql) # :nodoc:
|
11
|
+
!READ_QUERY.match?(sql)
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(sql, name = nil) #:nodoc:
|
15
|
+
if preventing_writes? && write_query?(sql)
|
16
|
+
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
17
|
+
end
|
18
|
+
|
19
|
+
materialize_transactions
|
20
|
+
|
21
|
+
log(sql, name) do
|
22
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
23
|
+
@connection.execute(sql)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def exec_query(sql, name = nil, binds = [], prepare: false)
|
29
|
+
if preventing_writes? && write_query?(sql)
|
30
|
+
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
31
|
+
end
|
32
|
+
|
33
|
+
materialize_transactions
|
34
|
+
|
35
|
+
type_casted_binds = type_casted_binds(binds)
|
36
|
+
|
37
|
+
log(sql, name, binds, type_casted_binds) do
|
38
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
39
|
+
# Don't cache statements if they are not prepared
|
40
|
+
unless prepare
|
41
|
+
stmt = @connection.prepare(sql)
|
42
|
+
begin
|
43
|
+
cols = stmt.columns
|
44
|
+
unless without_prepared_statement?(binds)
|
45
|
+
stmt.bind_params(type_casted_binds)
|
46
|
+
end
|
47
|
+
records = stmt.to_a
|
48
|
+
ensure
|
49
|
+
stmt.close
|
50
|
+
end
|
51
|
+
else
|
52
|
+
stmt = @statements[sql] ||= @connection.prepare(sql)
|
53
|
+
cols = stmt.columns
|
54
|
+
stmt.reset!
|
55
|
+
stmt.bind_params(type_casted_binds)
|
56
|
+
records = stmt.to_a
|
57
|
+
end
|
58
|
+
|
59
|
+
ActiveRecord::Result.new(cols, records)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def exec_delete(sql, name = "SQL", binds = [])
|
65
|
+
exec_query(sql, name, binds)
|
66
|
+
@connection.changes
|
67
|
+
end
|
68
|
+
alias :exec_update :exec_delete
|
69
|
+
|
70
|
+
def begin_db_transaction #:nodoc:
|
71
|
+
log("begin transaction", nil) { @connection.transaction }
|
72
|
+
end
|
73
|
+
|
74
|
+
def commit_db_transaction #:nodoc:
|
75
|
+
log("commit transaction", nil) { @connection.commit }
|
76
|
+
end
|
77
|
+
|
78
|
+
def exec_rollback_db_transaction #:nodoc:
|
79
|
+
log("rollback transaction", nil) { @connection.rollback }
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
private
|
84
|
+
def execute_batch(sql, name = nil)
|
85
|
+
if preventing_writes? && write_query?(sql)
|
86
|
+
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
87
|
+
end
|
88
|
+
|
89
|
+
materialize_transactions
|
90
|
+
|
91
|
+
log(sql, name) do
|
92
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
93
|
+
@connection.execute_batch2(sql)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def last_inserted_id(result)
|
99
|
+
@connection.last_insert_row_id
|
100
|
+
end
|
101
|
+
|
102
|
+
def build_fixture_statements(fixture_set)
|
103
|
+
fixture_set.flat_map do |table_name, fixtures|
|
104
|
+
next if fixtures.empty?
|
105
|
+
fixtures.map { |fixture| build_fixture_sql([fixture], table_name) }
|
106
|
+
end.compact
|
107
|
+
end
|
108
|
+
|
109
|
+
def build_truncate_statements(*table_names)
|
110
|
+
truncate_tables = table_names.map do |table_name|
|
111
|
+
"DELETE FROM #{quote_table_name(table_name)}"
|
112
|
+
end
|
113
|
+
combine_multi_statements(truncate_tables)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -13,11 +13,11 @@ module ActiveRecord
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def quote_table_name(name)
|
16
|
-
|
16
|
+
self.class.quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
|
17
17
|
end
|
18
18
|
|
19
19
|
def quote_column_name(name)
|
20
|
-
|
20
|
+
self.class.quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
|
21
21
|
end
|
22
22
|
|
23
23
|
def quoted_time(value)
|
@@ -45,6 +45,42 @@ module ActiveRecord
|
|
45
45
|
0
|
46
46
|
end
|
47
47
|
|
48
|
+
def column_name_matcher
|
49
|
+
COLUMN_NAME
|
50
|
+
end
|
51
|
+
|
52
|
+
def column_name_with_order_matcher
|
53
|
+
COLUMN_NAME_WITH_ORDER
|
54
|
+
end
|
55
|
+
|
56
|
+
COLUMN_NAME = /
|
57
|
+
\A
|
58
|
+
(
|
59
|
+
(?:
|
60
|
+
# "table_name"."column_name" | function(one or no argument)
|
61
|
+
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
|
62
|
+
)
|
63
|
+
(?:\s+AS\s+(?:\w+|"\w+"))?
|
64
|
+
)
|
65
|
+
(?:\s*,\s*\g<1>)*
|
66
|
+
\z
|
67
|
+
/ix
|
68
|
+
|
69
|
+
COLUMN_NAME_WITH_ORDER = /
|
70
|
+
\A
|
71
|
+
(
|
72
|
+
(?:
|
73
|
+
# "table_name"."column_name" | function(one or no argument)
|
74
|
+
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
|
75
|
+
)
|
76
|
+
(?:\s+ASC|\s+DESC)?
|
77
|
+
)
|
78
|
+
(?:\s*,\s*\g<1>)*
|
79
|
+
\z
|
80
|
+
/ix
|
81
|
+
|
82
|
+
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
83
|
+
|
48
84
|
private
|
49
85
|
|
50
86
|
def _type_cast(value)
|
@@ -52,6 +52,32 @@ module ActiveRecord
|
|
52
52
|
end.compact
|
53
53
|
end
|
54
54
|
|
55
|
+
def add_foreign_key(from_table, to_table, **options)
|
56
|
+
alter_table(from_table) do |definition|
|
57
|
+
to_table = strip_table_name_prefix_and_suffix(to_table)
|
58
|
+
definition.foreign_key(to_table, options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def remove_foreign_key(from_table, to_table = nil, **options)
|
63
|
+
to_table ||= options[:to_table]
|
64
|
+
options = options.except(:name, :to_table)
|
65
|
+
foreign_keys = foreign_keys(from_table)
|
66
|
+
|
67
|
+
fkey = foreign_keys.detect do |fk|
|
68
|
+
table = to_table || begin
|
69
|
+
table = options[:column].to_s.delete_suffix("_id")
|
70
|
+
Base.pluralize_table_names ? table.pluralize : table
|
71
|
+
end
|
72
|
+
table = strip_table_name_prefix_and_suffix(table)
|
73
|
+
fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
|
74
|
+
fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
|
75
|
+
end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
|
76
|
+
|
77
|
+
foreign_keys.delete(fkey)
|
78
|
+
alter_table(from_table, foreign_keys)
|
79
|
+
end
|
80
|
+
|
55
81
|
def create_schema_dumper(options)
|
56
82
|
SQLite3::SchemaDumper.create(self, options)
|
57
83
|
end
|
@@ -62,7 +88,7 @@ module ActiveRecord
|
|
62
88
|
end
|
63
89
|
|
64
90
|
def create_table_definition(*args)
|
65
|
-
SQLite3::TableDefinition.new(*args)
|
91
|
+
SQLite3::TableDefinition.new(self, *args)
|
66
92
|
end
|
67
93
|
|
68
94
|
def new_column_from_field(table_name, field)
|
@@ -79,7 +105,7 @@ module ActiveRecord
|
|
79
105
|
end
|
80
106
|
|
81
107
|
type_metadata = fetch_type_metadata(field["type"])
|
82
|
-
Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0,
|
108
|
+
Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, collation: field["collation"])
|
83
109
|
end
|
84
110
|
|
85
111
|
def data_source_sql(name = nil, type: nil)
|