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.

Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +455 -9
  3. data/README.rdoc +3 -1
  4. data/lib/active_record/associations/association.rb +18 -1
  5. data/lib/active_record/associations/builder/association.rb +14 -18
  6. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  7. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  8. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  9. data/lib/active_record/associations/builder/has_many.rb +2 -0
  10. data/lib/active_record/associations/builder/has_one.rb +35 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  12. data/lib/active_record/associations/collection_association.rb +5 -6
  13. data/lib/active_record/associations/collection_proxy.rb +13 -42
  14. data/lib/active_record/associations/has_many_association.rb +1 -9
  15. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  16. data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
  17. data/lib/active_record/associations/join_dependency.rb +10 -9
  18. data/lib/active_record/associations/preloader/association.rb +37 -34
  19. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  20. data/lib/active_record/associations/preloader.rb +11 -6
  21. data/lib/active_record/associations.rb +3 -2
  22. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  23. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  24. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  25. data/lib/active_record/attribute_methods/query.rb +2 -3
  26. data/lib/active_record/attribute_methods/read.rb +3 -9
  27. data/lib/active_record/attribute_methods/write.rb +6 -12
  28. data/lib/active_record/attribute_methods.rb +3 -53
  29. data/lib/active_record/attributes.rb +13 -0
  30. data/lib/active_record/autosave_association.rb +15 -5
  31. data/lib/active_record/base.rb +0 -1
  32. data/lib/active_record/callbacks.rb +3 -3
  33. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +124 -23
  34. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  35. data/lib/active_record/connection_adapters/abstract/database_statements.rb +101 -70
  36. data/lib/active_record/connection_adapters/abstract/query_cache.rb +11 -5
  37. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  38. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
  39. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
  40. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
  42. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  43. data/lib/active_record/connection_adapters/abstract_adapter.rb +108 -39
  44. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +93 -134
  45. data/lib/active_record/connection_adapters/column.rb +17 -13
  46. data/lib/active_record/connection_adapters/connection_specification.rb +1 -1
  47. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
  48. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
  49. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  50. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  51. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  52. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
  53. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  54. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  55. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  56. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  58. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  59. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  60. data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
  61. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  62. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
  63. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
  64. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  65. data/lib/active_record/connection_adapters/postgresql_adapter.rb +91 -24
  66. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  67. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  68. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  69. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +69 -118
  72. data/lib/active_record/connection_handling.rb +32 -16
  73. data/lib/active_record/core.rb +27 -20
  74. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  75. data/lib/active_record/database_configurations/url_config.rb +21 -16
  76. data/lib/active_record/database_configurations.rb +99 -50
  77. data/lib/active_record/dynamic_matchers.rb +1 -1
  78. data/lib/active_record/enum.rb +15 -0
  79. data/lib/active_record/errors.rb +18 -13
  80. data/lib/active_record/fixtures.rb +11 -6
  81. data/lib/active_record/gem_version.rb +1 -1
  82. data/lib/active_record/inheritance.rb +1 -1
  83. data/lib/active_record/insert_all.rb +179 -0
  84. data/lib/active_record/integration.rb +13 -1
  85. data/lib/active_record/internal_metadata.rb +5 -1
  86. data/lib/active_record/locking/optimistic.rb +3 -4
  87. data/lib/active_record/log_subscriber.rb +1 -1
  88. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  89. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  90. data/lib/active_record/middleware/database_selector.rb +75 -0
  91. data/lib/active_record/migration/command_recorder.rb +28 -14
  92. data/lib/active_record/migration/compatibility.rb +72 -63
  93. data/lib/active_record/migration.rb +62 -44
  94. data/lib/active_record/persistence.rb +212 -19
  95. data/lib/active_record/querying.rb +18 -14
  96. data/lib/active_record/railtie.rb +9 -1
  97. data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
  98. data/lib/active_record/railties/databases.rake +124 -25
  99. data/lib/active_record/reflection.rb +18 -32
  100. data/lib/active_record/relation/calculations.rb +40 -44
  101. data/lib/active_record/relation/delegation.rb +23 -31
  102. data/lib/active_record/relation/finder_methods.rb +13 -13
  103. data/lib/active_record/relation/merger.rb +11 -16
  104. data/lib/active_record/relation/query_attribute.rb +5 -3
  105. data/lib/active_record/relation/query_methods.rb +217 -68
  106. data/lib/active_record/relation/spawn_methods.rb +1 -1
  107. data/lib/active_record/relation/where_clause.rb +10 -10
  108. data/lib/active_record/relation.rb +184 -35
  109. data/lib/active_record/sanitization.rb +33 -4
  110. data/lib/active_record/schema.rb +1 -1
  111. data/lib/active_record/schema_dumper.rb +10 -1
  112. data/lib/active_record/schema_migration.rb +1 -1
  113. data/lib/active_record/scoping/default.rb +7 -15
  114. data/lib/active_record/scoping/named.rb +10 -2
  115. data/lib/active_record/scoping.rb +6 -7
  116. data/lib/active_record/statement_cache.rb +2 -2
  117. data/lib/active_record/store.rb +48 -0
  118. data/lib/active_record/table_metadata.rb +9 -13
  119. data/lib/active_record/tasks/database_tasks.rb +109 -6
  120. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  121. data/lib/active_record/test_databases.rb +1 -16
  122. data/lib/active_record/test_fixtures.rb +2 -2
  123. data/lib/active_record/timestamp.rb +35 -19
  124. data/lib/active_record/touch_later.rb +4 -2
  125. data/lib/active_record/transactions.rb +55 -45
  126. data/lib/active_record/type_caster/connection.rb +16 -10
  127. data/lib/active_record/validations/uniqueness.rb +4 -4
  128. data/lib/active_record/validations.rb +1 -0
  129. data/lib/active_record.rb +7 -1
  130. data/lib/arel/insert_manager.rb +3 -3
  131. data/lib/arel/nodes/and.rb +1 -1
  132. data/lib/arel/nodes/case.rb +1 -1
  133. data/lib/arel/nodes/comment.rb +29 -0
  134. data/lib/arel/nodes/select_core.rb +16 -12
  135. data/lib/arel/nodes/unary.rb +1 -0
  136. data/lib/arel/nodes/values_list.rb +2 -17
  137. data/lib/arel/nodes.rb +2 -1
  138. data/lib/arel/select_manager.rb +10 -10
  139. data/lib/arel/visitors/depth_first.rb +7 -2
  140. data/lib/arel/visitors/dot.rb +7 -2
  141. data/lib/arel/visitors/ibm_db.rb +13 -0
  142. data/lib/arel/visitors/informix.rb +6 -0
  143. data/lib/arel/visitors/mssql.rb +15 -1
  144. data/lib/arel/visitors/oracle12.rb +4 -5
  145. data/lib/arel/visitors/postgresql.rb +4 -10
  146. data/lib/arel/visitors/to_sql.rb +107 -131
  147. data/lib/arel/visitors/visitor.rb +9 -5
  148. data/lib/arel.rb +7 -0
  149. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  150. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  151. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  152. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  153. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  154. metadata +17 -13
  155. data/lib/active_record/collection_cache_key.rb +0 -53
  156. data/lib/arel/nodes/values.rb +0 -16
@@ -3,38 +3,34 @@
3
3
  module ActiveRecord
4
4
  # :stopdoc:
5
5
  module ConnectionAdapters
6
- class PostgreSQLTypeMetadata < DelegateClass(SqlTypeMetadata)
7
- undef to_yaml if method_defined?(:to_yaml)
6
+ module PostgreSQL
7
+ class TypeMetadata < DelegateClass(SqlTypeMetadata)
8
+ undef to_yaml if method_defined?(:to_yaml)
8
9
 
9
- attr_reader :oid, :fmod, :array
10
+ attr_reader :oid, :fmod
10
11
 
11
- def initialize(type_metadata, oid: nil, fmod: nil)
12
- super(type_metadata)
13
- @type_metadata = type_metadata
14
- @oid = oid
15
- @fmod = fmod
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
- protected
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 attributes_for_hash
36
- [self.class, @type_metadata, oid, fmod]
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?("does not exist")
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
- # Clears the prepared statements cache.
255
- def clear_cache!
256
- @lock.synchronize do
257
- @statements.clear
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
- postgresql_version >= 90400
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
- res = exec_query("SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", "SCHEMA")
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 postgresql_version
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
- private
427
- def check_version
428
- if postgresql_version < 90300
429
- raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.3."
430
- end
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"] = @columns
28
- coder["columns_hash"] = @columns_hash
29
- coder["primary_keys"] = @primary_keys
30
- coder["data_sources"] = @data_sources
31
- coder["version"] = connection.migration_context.current_version
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 = coder["columns"]
36
- @columns_hash = coder["columns_hash"]
37
- @primary_keys = coder["primary_keys"]
38
- @data_sources = coder["data_sources"]
39
- @version = coder["version"]
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].map(&:size).inject :+
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
- attributes_for_hash == other.attributes_for_hash
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
- attributes_for_hash.hash
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
- @quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
16
+ self.class.quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
17
17
  end
18
18
 
19
19
  def quote_column_name(name)
20
- @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
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, table_name, nil, field["collation"])
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)