activerecord 5.0.0 → 5.0.7.2

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 (101) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +431 -2
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/aggregations.rb +4 -2
  5. data/lib/active_record/association_relation.rb +4 -1
  6. data/lib/active_record/associations/association.rb +11 -1
  7. data/lib/active_record/associations/association_scope.rb +1 -1
  8. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +4 -2
  9. data/lib/active_record/associations/builder/singular_association.rb +10 -1
  10. data/lib/active_record/associations/collection_association.rb +56 -48
  11. data/lib/active_record/associations/collection_proxy.rb +38 -20
  12. data/lib/active_record/associations/has_many_association.rb +1 -7
  13. data/lib/active_record/associations/has_many_through_association.rb +2 -4
  14. data/lib/active_record/associations/join_dependency/join_association.rb +6 -11
  15. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  16. data/lib/active_record/associations/join_dependency.rb +10 -4
  17. data/lib/active_record/associations/preloader/association.rb +24 -37
  18. data/lib/active_record/associations/preloader/collection_association.rb +0 -1
  19. data/lib/active_record/associations/preloader/singular_association.rb +0 -1
  20. data/lib/active_record/associations/preloader/through_association.rb +10 -4
  21. data/lib/active_record/associations/singular_association.rb +8 -2
  22. data/lib/active_record/associations/through_association.rb +1 -1
  23. data/lib/active_record/associations.rb +38 -17
  24. data/lib/active_record/attribute.rb +3 -3
  25. data/lib/active_record/attribute_methods/primary_key.rb +14 -1
  26. data/lib/active_record/attribute_methods/read.rb +1 -1
  27. data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -2
  28. data/lib/active_record/attribute_methods.rb +3 -7
  29. data/lib/active_record/attribute_set/builder.rb +37 -13
  30. data/lib/active_record/attribute_set.rb +2 -0
  31. data/lib/active_record/attributes.rb +3 -3
  32. data/lib/active_record/autosave_association.rb +15 -11
  33. data/lib/active_record/base.rb +1 -1
  34. data/lib/active_record/collection_cache_key.rb +16 -6
  35. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +41 -33
  36. data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
  37. data/lib/active_record/connection_adapters/abstract/query_cache.rb +37 -2
  38. data/lib/active_record/connection_adapters/abstract/quoting.rb +14 -5
  39. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +11 -14
  40. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +68 -56
  41. data/lib/active_record/connection_adapters/abstract_adapter.rb +36 -12
  42. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +63 -60
  43. data/lib/active_record/connection_adapters/column.rb +1 -1
  44. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  45. data/lib/active_record/connection_adapters/mysql/database_statements.rb +8 -25
  46. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  47. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -6
  48. data/lib/active_record/connection_adapters/postgresql/column.rb +28 -1
  49. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -0
  50. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +12 -2
  51. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  52. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  53. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +10 -0
  54. data/lib/active_record/connection_adapters/postgresql/quoting.rb +32 -3
  55. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +18 -8
  56. data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -2
  57. data/lib/active_record/connection_adapters/postgresql_adapter.rb +25 -19
  58. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  59. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +4 -4
  60. data/lib/active_record/core.rb +6 -4
  61. data/lib/active_record/enum.rb +8 -5
  62. data/lib/active_record/explain.rb +20 -9
  63. data/lib/active_record/fixtures.rb +5 -5
  64. data/lib/active_record/gem_version.rb +2 -2
  65. data/lib/active_record/integration.rb +13 -10
  66. data/lib/active_record/log_subscriber.rb +19 -17
  67. data/lib/active_record/migration.rb +35 -14
  68. data/lib/active_record/model_schema.rb +161 -52
  69. data/lib/active_record/no_touching.rb +4 -0
  70. data/lib/active_record/persistence.rb +41 -20
  71. data/lib/active_record/query_cache.rb +15 -17
  72. data/lib/active_record/querying.rb +3 -3
  73. data/lib/active_record/railties/controller_runtime.rb +1 -1
  74. data/lib/active_record/railties/databases.rake +9 -21
  75. data/lib/active_record/reflection.rb +20 -0
  76. data/lib/active_record/relation/batches.rb +10 -10
  77. data/lib/active_record/relation/calculations.rb +16 -12
  78. data/lib/active_record/relation/delegation.rb +2 -1
  79. data/lib/active_record/relation/finder_methods.rb +13 -11
  80. data/lib/active_record/relation/query_methods.rb +3 -3
  81. data/lib/active_record/relation.rb +12 -5
  82. data/lib/active_record/result.rb +7 -1
  83. data/lib/active_record/sanitization.rb +11 -1
  84. data/lib/active_record/schema_dumper.rb +10 -17
  85. data/lib/active_record/scoping/default.rb +5 -1
  86. data/lib/active_record/scoping/named.rb +18 -6
  87. data/lib/active_record/scoping.rb +4 -3
  88. data/lib/active_record/serialization.rb +1 -1
  89. data/lib/active_record/statement_cache.rb +2 -2
  90. data/lib/active_record/table_metadata.rb +4 -3
  91. data/lib/active_record/tasks/database_tasks.rb +14 -11
  92. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  93. data/lib/active_record/touch_later.rb +6 -1
  94. data/lib/active_record/transactions.rb +1 -1
  95. data/lib/active_record/type/internal/abstract_json.rb +5 -1
  96. data/lib/active_record/validations/uniqueness.rb +3 -4
  97. data/lib/active_record.rb +3 -2
  98. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  99. data/lib/rails/generators/active_record/migration.rb +8 -0
  100. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  101. metadata +7 -8
@@ -68,7 +68,7 @@ module ActiveRecord
68
68
  @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
69
69
 
70
70
  if version < '5.0.0'
71
- raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.0."
71
+ raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.0."
72
72
  end
73
73
  end
74
74
 
@@ -81,7 +81,7 @@ module ActiveRecord
81
81
  end
82
82
 
83
83
  def version #:nodoc:
84
- @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
84
+ @version ||= Version.new(version_string)
85
85
  end
86
86
 
87
87
  def mariadb? # :nodoc:
@@ -324,7 +324,7 @@ module ActiveRecord
324
324
 
325
325
  def data_sources
326
326
  sql = "SELECT table_name FROM information_schema.tables "
327
- sql << "WHERE table_schema = #{quote(@config[:database])}"
327
+ sql << "WHERE table_schema = DATABASE()"
328
328
 
329
329
  select_values(sql, 'SCHEMA')
330
330
  end
@@ -350,7 +350,7 @@ module ActiveRecord
350
350
  schema, name = extract_schema_qualified_name(table_name)
351
351
 
352
352
  sql = "SELECT table_name FROM information_schema.tables "
353
- sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
353
+ sql << "WHERE table_schema = #{schema} AND table_name = #{name}"
354
354
 
355
355
  select_values(sql, 'SCHEMA').any?
356
356
  end
@@ -365,7 +365,7 @@ module ActiveRecord
365
365
  schema, name = extract_schema_qualified_name(view_name)
366
366
 
367
367
  sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
368
- sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
368
+ sql << " AND table_schema = #{schema} AND table_name = #{name}"
369
369
 
370
370
  select_values(sql, 'SCHEMA').any?
371
371
  end
@@ -383,11 +383,11 @@ module ActiveRecord
383
383
  mysql_index_type = row[:Index_type].downcase.to_sym
384
384
  index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
385
385
  index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
386
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using, row[:Index_comment].presence)
386
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], {}, nil, nil, index_type, index_using, row[:Index_comment].presence)
387
387
  end
388
388
 
389
389
  indexes.last.columns << row[:Column_name]
390
- indexes.last.lengths << row[:Sub_part]
390
+ indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
391
391
  end
392
392
  end
393
393
 
@@ -399,8 +399,8 @@ module ActiveRecord
399
399
  table_name = table_name.to_s
400
400
  column_definitions(table_name).map do |field|
401
401
  type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
402
- if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
403
- default, default_function = nil, field[:Default]
402
+ if type_metadata.type == :datetime && field[:Default] =~ /\ACURRENT_TIMESTAMP(?:\(\))?\z/i
403
+ default, default_function = nil, "CURRENT_TIMESTAMP"
404
404
  else
405
405
  default, default_function = field[:Default], nil
406
406
  end
@@ -409,10 +409,13 @@ module ActiveRecord
409
409
  end
410
410
 
411
411
  def table_comment(table_name) # :nodoc:
412
+ schema, name = extract_schema_qualified_name(table_name)
413
+
412
414
  select_value(<<-SQL.strip_heredoc, 'SCHEMA')
413
415
  SELECT table_comment
414
416
  FROM information_schema.tables
415
- WHERE table_name=#{quote(table_name)}
417
+ WHERE table_schema = #{schema}
418
+ AND table_name = #{name}
416
419
  SQL
417
420
  end
418
421
 
@@ -460,7 +463,6 @@ module ActiveRecord
460
463
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
461
464
  # In that case, +options+ and the block will be used by create_table.
462
465
  def drop_table(table_name, options = {})
463
- create_table_info_cache.delete(table_name) if create_table_info_cache.key?(table_name)
464
466
  execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
465
467
  end
466
468
 
@@ -506,7 +508,7 @@ module ActiveRecord
506
508
  end
507
509
 
508
510
  def add_sql_comment!(sql, comment) # :nodoc:
509
- sql << " COMMENT #{quote(comment)}" if comment
511
+ sql << " COMMENT #{quote(comment)}" if comment.present?
510
512
  sql
511
513
  end
512
514
 
@@ -515,19 +517,22 @@ module ActiveRecord
515
517
 
516
518
  schema, name = extract_schema_qualified_name(table_name)
517
519
 
518
- fk_info = select_all <<-SQL.strip_heredoc
519
- SELECT fk.referenced_table_name as 'to_table'
520
- ,fk.referenced_column_name as 'primary_key'
521
- ,fk.column_name as 'column'
522
- ,fk.constraint_name as 'name'
520
+ fk_info = select_all(<<-SQL.strip_heredoc, 'SCHEMA')
521
+ SELECT fk.referenced_table_name AS 'to_table',
522
+ fk.referenced_column_name AS 'primary_key',
523
+ fk.column_name AS 'column',
524
+ fk.constraint_name AS 'name',
525
+ rc.update_rule AS 'on_update',
526
+ rc.delete_rule AS 'on_delete'
523
527
  FROM information_schema.key_column_usage fk
524
- WHERE fk.referenced_column_name is not null
525
- AND fk.table_schema = #{quote(schema)}
526
- AND fk.table_name = #{quote(name)}
528
+ JOIN information_schema.referential_constraints rc
529
+ USING (constraint_schema, constraint_name)
530
+ WHERE fk.referenced_column_name IS NOT NULL
531
+ AND fk.table_schema = #{schema}
532
+ AND fk.table_name = #{name}
533
+ AND rc.table_name = #{name}
527
534
  SQL
528
535
 
529
- create_table_info = create_table_info(table_name)
530
-
531
536
  fk_info.map do |row|
532
537
  options = {
533
538
  column: row['column'],
@@ -535,14 +540,16 @@ module ActiveRecord
535
540
  primary_key: row['primary_key']
536
541
  }
537
542
 
538
- options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
539
- options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
543
+ options[:on_update] = extract_foreign_key_action(row['on_update'])
544
+ options[:on_delete] = extract_foreign_key_action(row['on_delete'])
540
545
 
541
546
  ForeignKeyDefinition.new(table_name, row['to_table'], options)
542
547
  end
543
548
  end
544
549
 
545
- def table_options(table_name)
550
+ def table_options(table_name) # :nodoc:
551
+ table_options = {}
552
+
546
553
  create_table_info = create_table_info(table_name)
547
554
 
548
555
  # strip create_definitions and partition_options
@@ -551,10 +558,14 @@ module ActiveRecord
551
558
  # strip AUTO_INCREMENT
552
559
  raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
553
560
 
561
+ table_options[:options] = raw_table_options
562
+
554
563
  # strip COMMENT
555
- raw_table_options.sub!(/ COMMENT='.+'/, '')
564
+ if raw_table_options.sub!(/ COMMENT='.+'/, '')
565
+ table_options[:comment] = table_comment(table_name)
566
+ end
556
567
 
557
- raw_table_options
568
+ table_options
558
569
  end
559
570
 
560
571
  # Maps logical Rails types to MySQL-specific data types.
@@ -596,8 +607,8 @@ module ActiveRecord
596
607
  SELECT column_name
597
608
  FROM information_schema.key_column_usage
598
609
  WHERE constraint_name = 'PRIMARY'
599
- AND table_schema = #{quote(schema)}
600
- AND table_name = #{quote(name)}
610
+ AND table_schema = #{schema}
611
+ AND table_name = #{name}
601
612
  ORDER BY ordinal_position
602
613
  SQL
603
614
  end
@@ -682,7 +693,7 @@ module ActiveRecord
682
693
 
683
694
  def register_integer_type(mapping, key, options) # :nodoc:
684
695
  mapping.register_type(key) do |sql_type|
685
- if /\bunsigned\z/ === sql_type
696
+ if /\bunsigned\b/ === sql_type
686
697
  Type::UnsignedInteger.new(options)
687
698
  else
688
699
  Type::Integer.new(options)
@@ -702,29 +713,23 @@ module ActiveRecord
702
713
  MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
703
714
  end
704
715
 
705
- def add_index_length(option_strings, column_names, options = {})
706
- if options.is_a?(Hash) && length = options[:length]
716
+ def add_index_length(quoted_columns, **options)
717
+ if length = options[:length]
707
718
  case length
708
719
  when Hash
709
- column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
720
+ length = length.symbolize_keys
721
+ quoted_columns.each { |name, column| column << "(#{length[name]})" if length[name].present? }
710
722
  when Integer
711
- column_names.each {|name| option_strings[name] += "(#{length})"}
723
+ quoted_columns.each { |name, column| column << "(#{length})" }
712
724
  end
713
725
  end
714
726
 
715
- return option_strings
727
+ quoted_columns
716
728
  end
717
729
 
718
- def quoted_columns_for_index(column_names, options = {})
719
- option_strings = Hash[column_names.map {|name| [name, '']}]
720
-
721
- # add index length
722
- option_strings = add_index_length(option_strings, column_names, options)
723
-
724
- # add index sort order
725
- option_strings = add_index_sort_order(option_strings, column_names, options)
726
-
727
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
730
+ def add_options_for_index_columns(quoted_columns, **options)
731
+ quoted_columns = add_index_length(quoted_columns, options)
732
+ super
728
733
  end
729
734
 
730
735
  def translate_exception(exception, message)
@@ -831,9 +836,9 @@ module ActiveRecord
831
836
  variables['sql_auto_is_null'] = 0
832
837
 
833
838
  # Increase timeout so the server doesn't disconnect us.
834
- wait_timeout = @config[:wait_timeout]
839
+ wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
835
840
  wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
836
- variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
841
+ variables["wait_timeout"] = wait_timeout
837
842
 
838
843
  defaults = [':default', :default].to_set
839
844
 
@@ -883,21 +888,15 @@ module ActiveRecord
883
888
  end
884
889
  end
885
890
 
886
- def extract_foreign_key_action(structure, name, action) # :nodoc:
887
- if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
888
- case $1
889
- when 'CASCADE'; :cascade
890
- when 'SET NULL'; :nullify
891
- end
891
+ def extract_foreign_key_action(specifier) # :nodoc:
892
+ case specifier
893
+ when 'CASCADE'; :cascade
894
+ when 'SET NULL'; :nullify
892
895
  end
893
896
  end
894
897
 
895
- def create_table_info_cache # :nodoc:
896
- @create_table_info_cache ||= {}
897
- end
898
-
899
898
  def create_table_info(table_name) # :nodoc:
900
- create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
899
+ select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
901
900
  end
902
901
 
903
902
  def create_table_definition(*args) # :nodoc:
@@ -905,8 +904,8 @@ module ActiveRecord
905
904
  end
906
905
 
907
906
  def extract_schema_qualified_name(string) # :nodoc:
908
- schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
909
- schema, name = @config[:database], schema unless name
907
+ schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/).map { |s| quote(s) }
908
+ schema, name = "DATABASE()", schema unless name
910
909
  [schema, name]
911
910
  end
912
911
 
@@ -941,6 +940,10 @@ module ActiveRecord
941
940
  end
942
941
  end
943
942
 
943
+ def version_string
944
+ full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
945
+ end
946
+
944
947
  class MysqlJson < Type::Internal::AbstractJson # :nodoc:
945
948
  def changed_in_place?(raw_old_value, new_value)
946
949
  # Normalization is required because MySQL JSON data format includes
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
14
14
  # +sql_type_metadata+ is various information about the type of the column
15
15
  # +null+ determines if this column allows +NULL+ values.
16
- def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil)
16
+ def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil, **)
17
17
  @name = name.freeze
18
18
  @table_name = table_name
19
19
  @sql_type_metadata = sql_type_metadata
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  end
21
21
 
22
22
  def unsigned?
23
- /\bunsigned\z/ === sql_type
23
+ /\A(?:enum|set)\b/ !~ sql_type && /\bunsigned\b/ === sql_type
24
24
  end
25
25
 
26
26
  def case_sensitive?
@@ -13,19 +13,6 @@ module ActiveRecord
13
13
  result
14
14
  end
15
15
 
16
- # Returns a record hash with the column names as keys and column values
17
- # as values.
18
- def select_one(arel, name = nil, binds = [])
19
- arel, binds = binds_from_relation(arel, binds)
20
- @connection.query_options.merge!(as: :hash)
21
- select_result(to_sql(arel, binds), name, binds) do |result|
22
- @connection.next_result while @connection.more_results?
23
- result.first
24
- end
25
- ensure
26
- @connection.query_options.merge!(as: :array)
27
- end
28
-
29
16
  # Returns an array of arrays containing the field values.
30
17
  # Order is the same as that returned by +columns+.
31
18
  def select_rows(sql, name = nil, binds = [])
@@ -37,11 +24,9 @@ module ActiveRecord
37
24
 
38
25
  # Executes the SQL statement in the context of this connection.
39
26
  def execute(sql, name = nil)
40
- if @connection
41
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
42
- # made since we established the connection
43
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
44
- end
27
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
28
+ # made since we established the connection
29
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
45
30
 
46
31
  super
47
32
  end
@@ -84,15 +69,13 @@ module ActiveRecord
84
69
  end
85
70
 
86
71
  def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
87
- if @connection
88
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
89
- # made since we established the connection
90
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
91
- end
72
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
73
+ # made since we established the connection
74
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
92
75
 
93
- type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
76
+ type_casted_binds = type_casted_binds(binds)
94
77
 
95
- log(sql, name, binds) do
78
+ log(sql, name, binds, type_casted_binds) do
96
79
  if cache_stmt
97
80
  cache = @statements[sql] ||= {
98
81
  stmt: @connection.prepare(sql)
@@ -2,7 +2,7 @@ module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  module MySQL
4
4
  module Quoting # :nodoc:
5
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
5
+ QUOTED_TRUE, QUOTED_FALSE = '1'.freeze, '0'.freeze
6
6
 
7
7
  def quote_column_name(name)
8
8
  @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
@@ -1,7 +1,7 @@
1
1
  require 'active_record/connection_adapters/abstract_mysql_adapter'
2
2
  require 'active_record/connection_adapters/mysql/database_statements'
3
3
 
4
- gem 'mysql2', '>= 0.3.18', '< 0.5'
4
+ gem 'mysql2', '>= 0.3.18', '< 0.6.0'
5
5
  require 'mysql2'
6
6
  raise 'mysql2 0.4.3 is not supported. Please upgrade to 0.4.4+' if Mysql2::VERSION == '0.4.3'
7
7
 
@@ -90,7 +90,6 @@ module ActiveRecord
90
90
  #++
91
91
 
92
92
  def active?
93
- return false unless @connection
94
93
  @connection.ping
95
94
  end
96
95
 
@@ -105,10 +104,7 @@ module ActiveRecord
105
104
  # Otherwise, this method does nothing.
106
105
  def disconnect!
107
106
  super
108
- unless @connection.nil?
109
- @connection.close
110
- @connection = nil
111
- end
107
+ @connection.close
112
108
  end
113
109
 
114
110
  private
@@ -5,11 +5,38 @@ module ActiveRecord
5
5
  delegate :array, :oid, :fmod, to: :sql_type_metadata
6
6
  alias :array? :array
7
7
 
8
+ def initialize(*, max_identifier_length: 63, **)
9
+ super
10
+ @max_identifier_length = max_identifier_length
11
+ end
12
+
8
13
  def serial?
9
14
  return unless default_function
10
15
 
11
- %r{\Anextval\('"?#{table_name}_#{name}_seq"?'::regclass\)\z} === default_function
16
+ if %r{\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z} =~ default_function
17
+ sequence_name_from_parts(table_name, name, suffix) == sequence_name
18
+ end
12
19
  end
20
+
21
+ protected
22
+ attr_reader :max_identifier_length
23
+
24
+ private
25
+ def sequence_name_from_parts(table_name, column_name, suffix)
26
+ over_length = [table_name, column_name, suffix].map(&:length).sum + 2 - max_identifier_length
27
+
28
+ if over_length > 0
29
+ column_name_length = [(max_identifier_length - suffix.length - 2) / 2, column_name.length].min
30
+ over_length -= column_name.length - column_name_length
31
+ column_name = column_name[0, column_name_length - [over_length, 0].min]
32
+ end
33
+
34
+ if over_length > 0
35
+ table_name = table_name[0, table_name.length - over_length]
36
+ end
37
+
38
+ "#{table_name}_#{column_name}_#{suffix}"
39
+ end
13
40
  end
14
41
  end
15
42
  end
@@ -124,6 +124,8 @@ module ActiveRecord
124
124
  pk = primary_key(table_ref) if table_ref
125
125
  end
126
126
 
127
+ pk = suppress_composite_primary_key(pk)
128
+
127
129
  if pk && use_insert_returning?
128
130
  sql = "#{sql} RETURNING #{quote_column_name(pk)}"
129
131
  end
@@ -164,6 +166,12 @@ module ActiveRecord
164
166
  def exec_rollback_db_transaction
165
167
  execute "ROLLBACK"
166
168
  end
169
+
170
+ private
171
+
172
+ def suppress_composite_primary_key(pk)
173
+ pk unless pk.is_a?(Array)
174
+ end
167
175
  end
168
176
  end
169
177
  end
@@ -5,6 +5,8 @@ module ActiveRecord
5
5
  class Array < Type::Value # :nodoc:
6
6
  include Type::Helpers::Mutable
7
7
 
8
+ Data = Struct.new(:encoder, :values) # :nodoc:
9
+
8
10
  attr_reader :subtype, :delimiter
9
11
  delegate :type, :user_input_in_time_zone, :limit, to: :subtype
10
12
 
@@ -17,8 +19,11 @@ module ActiveRecord
17
19
  end
18
20
 
19
21
  def deserialize(value)
20
- if value.is_a?(::String)
22
+ case value
23
+ when ::String
21
24
  type_cast_array(@pg_decoder.decode(value), :deserialize)
25
+ when Data
26
+ type_cast_array(value.values, :deserialize)
22
27
  else
23
28
  super
24
29
  end
@@ -33,7 +38,8 @@ module ActiveRecord
33
38
 
34
39
  def serialize(value)
35
40
  if value.is_a?(::Array)
36
- @pg_encoder.encode(type_cast_array(value, :serialize))
41
+ casted_values = type_cast_array(value, :serialize)
42
+ Data.new(@pg_encoder, casted_values)
37
43
  else
38
44
  super
39
45
  end
@@ -54,6 +60,10 @@ module ActiveRecord
54
60
  value.map(&block)
55
61
  end
56
62
 
63
+ def changed_in_place?(raw_old_value, new_value)
64
+ deserialize(raw_old_value) != new_value
65
+ end
66
+
57
67
  private
58
68
 
59
69
  def type_cast_array(value, method)
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  :bit
8
8
  end
9
9
 
10
- def cast(value)
10
+ def cast_value(value)
11
11
  if ::String === value
12
12
  case value
13
13
  when /^0x/i
@@ -16,7 +16,7 @@ module ActiveRecord
16
16
  value # Bit-string notation
17
17
  end
18
18
  else
19
- value
19
+ value.to_s
20
20
  end
21
21
  end
22
22
 
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  def deserialize(value)
7
7
  return if value.nil?
8
8
  return value.to_s if value.is_a?(Type::Binary::Data)
9
- PGconn.unescape_bytea(super)
9
+ PG::Connection.unescape_bytea(super)
10
10
  end
11
11
  end
12
12
  end
@@ -24,6 +24,8 @@ module ActiveRecord
24
24
  def serialize(value)
25
25
  if value.is_a?(::Hash)
26
26
  value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(', ')
27
+ elsif value.respond_to?(:to_unsafe_h)
28
+ serialize(value.to_unsafe_h)
27
29
  else
28
30
  value
29
31
  end
@@ -33,6 +35,14 @@ module ActiveRecord
33
35
  ActiveRecord::Store::StringKeyedHashAccessor
34
36
  end
35
37
 
38
+ # Will compare the Hash equivalents of +raw_old_value+ and +new_value+.
39
+ # By comparing hashes, this avoids an edge case where the order of
40
+ # the keys change between the two hashes, and they would not be marked
41
+ # as equal.
42
+ def changed_in_place?(raw_old_value, new_value)
43
+ deserialize(raw_old_value) != new_value
44
+ end
45
+
36
46
  private
37
47
 
38
48
  HstorePair = begin
@@ -33,7 +33,7 @@ module ActiveRecord
33
33
 
34
34
  # Quotes schema names for use in SQL queries.
35
35
  def quote_schema_name(name)
36
- PGconn.quote_ident(name)
36
+ PG::Connection.quote_ident(name)
37
37
  end
38
38
 
39
39
  def quote_table_name_for_assignment(table, attr)
@@ -42,7 +42,7 @@ module ActiveRecord
42
42
 
43
43
  # Quotes column names for use in SQL queries.
44
44
  def quote_column_name(name) # :nodoc:
45
- @quoted_column_names[name] ||= PGconn.quote_ident(super)
45
+ @quoted_column_names[name] ||= PG::Connection.quote_ident(super)
46
46
  end
47
47
 
48
48
  # Quote date/time values for use in SQL input.
@@ -92,6 +92,8 @@ module ActiveRecord
92
92
  else
93
93
  super
94
94
  end
95
+ when OID::Array::Data
96
+ _quote(encode_array(value))
95
97
  else
96
98
  super
97
99
  end
@@ -101,15 +103,42 @@ module ActiveRecord
101
103
  case value
102
104
  when Type::Binary::Data
103
105
  # Return a bind param hash with format as binary.
104
- # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc
106
+ # See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc
105
107
  # for more information
106
108
  { value: value.to_s, format: 1 }
107
109
  when OID::Xml::Data, OID::Bit::Data
108
110
  value.to_s
111
+ when OID::Array::Data
112
+ encode_array(value)
109
113
  else
110
114
  super
111
115
  end
112
116
  end
117
+
118
+ def encode_array(array_data)
119
+ encoder = array_data.encoder
120
+ values = type_cast_array(array_data.values)
121
+
122
+ result = encoder.encode(values)
123
+ if encoding = determine_encoding_of_strings_in_array(values)
124
+ result.force_encoding(encoding)
125
+ end
126
+ result
127
+ end
128
+
129
+ def determine_encoding_of_strings_in_array(value)
130
+ case value
131
+ when ::Array then determine_encoding_of_strings_in_array(value.first)
132
+ when ::String then value.encoding
133
+ end
134
+ end
135
+
136
+ def type_cast_array(values)
137
+ case values
138
+ when ::Array then values.map { |item| type_cast_array(item) }
139
+ else _type_cast(values)
140
+ end
141
+ end
113
142
  end
114
143
  end
115
144
  end