activerecord 7.2.3 → 8.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +391 -958
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +1 -0
  5. data/lib/active_record/associations/association.rb +34 -10
  6. data/lib/active_record/associations/builder/association.rb +7 -6
  7. data/lib/active_record/associations/collection_association.rb +1 -1
  8. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  9. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  10. data/lib/active_record/associations/preloader/association.rb +2 -2
  11. data/lib/active_record/associations/singular_association.rb +8 -3
  12. data/lib/active_record/associations.rb +34 -4
  13. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  14. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  15. data/lib/active_record/attribute_methods/query.rb +34 -0
  16. data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
  17. data/lib/active_record/autosave_association.rb +69 -27
  18. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
  19. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  20. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  21. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +6 -15
  22. data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
  23. data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -2
  24. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  25. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  26. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -2
  27. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +34 -7
  28. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  29. data/lib/active_record/connection_adapters/abstract_adapter.rb +31 -43
  30. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +21 -40
  31. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  32. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  33. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
  34. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
  35. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
  36. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  37. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
  38. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  39. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  40. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  41. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  42. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +6 -12
  43. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +2 -1
  44. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +59 -16
  45. data/lib/active_record/connection_adapters/postgresql_adapter.rb +46 -96
  46. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  47. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
  48. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  49. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  50. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +9 -1
  51. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
  52. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  53. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  54. data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
  55. data/lib/active_record/connection_adapters.rb +0 -56
  56. data/lib/active_record/connection_handling.rb +23 -1
  57. data/lib/active_record/core.rb +29 -14
  58. data/lib/active_record/database_configurations/database_config.rb +4 -0
  59. data/lib/active_record/database_configurations/hash_config.rb +16 -2
  60. data/lib/active_record/encryption/config.rb +3 -1
  61. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  62. data/lib/active_record/encryption/encrypted_attribute_type.rb +10 -1
  63. data/lib/active_record/encryption/encryptor.rb +16 -8
  64. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  65. data/lib/active_record/encryption/scheme.rb +8 -1
  66. data/lib/active_record/enum.rb +9 -22
  67. data/lib/active_record/errors.rb +13 -5
  68. data/lib/active_record/fixtures.rb +0 -2
  69. data/lib/active_record/future_result.rb +13 -9
  70. data/lib/active_record/gem_version.rb +3 -3
  71. data/lib/active_record/insert_all.rb +1 -1
  72. data/lib/active_record/locking/optimistic.rb +1 -1
  73. data/lib/active_record/log_subscriber.rb +5 -11
  74. data/lib/active_record/migration/command_recorder.rb +31 -11
  75. data/lib/active_record/migration/compatibility.rb +5 -2
  76. data/lib/active_record/migration.rb +38 -42
  77. data/lib/active_record/model_schema.rb +3 -4
  78. data/lib/active_record/nested_attributes.rb +4 -6
  79. data/lib/active_record/persistence.rb +128 -130
  80. data/lib/active_record/query_logs.rb +102 -50
  81. data/lib/active_record/query_logs_formatter.rb +17 -28
  82. data/lib/active_record/querying.rb +8 -8
  83. data/lib/active_record/railtie.rb +2 -26
  84. data/lib/active_record/railties/databases.rake +11 -35
  85. data/lib/active_record/reflection.rb +18 -21
  86. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  87. data/lib/active_record/relation/batches.rb +132 -72
  88. data/lib/active_record/relation/calculations.rb +40 -39
  89. data/lib/active_record/relation/delegation.rb +25 -14
  90. data/lib/active_record/relation/finder_methods.rb +18 -18
  91. data/lib/active_record/relation/merger.rb +8 -8
  92. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  93. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  94. data/lib/active_record/relation/predicate_builder.rb +13 -0
  95. data/lib/active_record/relation/query_methods.rb +105 -61
  96. data/lib/active_record/relation/spawn_methods.rb +7 -7
  97. data/lib/active_record/relation.rb +79 -61
  98. data/lib/active_record/result.rb +66 -4
  99. data/lib/active_record/sanitization.rb +7 -6
  100. data/lib/active_record/schema_dumper.rb +5 -0
  101. data/lib/active_record/schema_migration.rb +2 -1
  102. data/lib/active_record/scoping/named.rb +5 -2
  103. data/lib/active_record/statement_cache.rb +14 -14
  104. data/lib/active_record/store.rb +7 -3
  105. data/lib/active_record/table_metadata.rb +1 -3
  106. data/lib/active_record/tasks/database_tasks.rb +69 -60
  107. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  108. data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -1
  109. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  110. data/lib/active_record/test_databases.rb +1 -1
  111. data/lib/active_record/test_fixtures.rb +12 -0
  112. data/lib/active_record/token_for.rb +1 -1
  113. data/lib/active_record/transactions.rb +5 -6
  114. data/lib/active_record/validations/uniqueness.rb +8 -8
  115. data/lib/active_record.rb +21 -48
  116. data/lib/arel/collectors/bind.rb +2 -2
  117. data/lib/arel/collectors/sql_string.rb +1 -1
  118. data/lib/arel/collectors/substitute_binds.rb +2 -2
  119. data/lib/arel/nodes/binary.rb +1 -1
  120. data/lib/arel/nodes/node.rb +1 -1
  121. data/lib/arel/nodes/sql_literal.rb +1 -1
  122. data/lib/arel/table.rb +3 -7
  123. metadata +9 -10
  124. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -13,49 +13,10 @@ module ActiveRecord
13
13
  end
14
14
  end
15
15
 
16
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
17
- if without_prepared_statement?(binds)
18
- execute_and_free(sql, name, async: async, allow_retry: allow_retry) do |result|
19
- if result
20
- build_result(columns: result.fields, rows: result.to_a)
21
- else
22
- build_result(columns: [], rows: [])
23
- end
24
- end
25
- else
26
- exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async, allow_retry: allow_retry) do |_, result|
27
- if result
28
- build_result(columns: result.fields, rows: result.to_a)
29
- else
30
- build_result(columns: [], rows: [])
31
- end
32
- end
33
- end
34
- end
35
-
36
- def exec_delete(sql, name = nil, binds = []) # :nodoc:
37
- if without_prepared_statement?(binds)
38
- with_raw_connection do |conn|
39
- @affected_rows_before_warnings = nil
40
- execute_and_free(sql, name) { @affected_rows_before_warnings || conn.affected_rows }
41
- end
42
- else
43
- exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
44
- end
45
- end
46
- alias :exec_update :exec_delete
47
-
48
16
  private
49
- def sync_timezone_changes(raw_connection)
50
- raw_connection.query_options[:database_timezone] = default_timezone
51
- end
52
-
53
- def execute_batch(statements, name = nil)
54
- statements = statements.map { |sql| transform_query(sql) }
17
+ def execute_batch(statements, name = nil, **kwargs)
55
18
  combine_multi_statements(statements).each do |statement|
56
- with_raw_connection do |conn|
57
- raw_execute(statement, name)
58
- end
19
+ raw_execute(statement, name, batch: true, **kwargs)
59
20
  end
60
21
  end
61
22
 
@@ -77,73 +38,102 @@ module ActiveRecord
77
38
  end
78
39
  end
79
40
 
80
- def with_multi_statements
81
- if multi_statements_enabled?
82
- return yield
41
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
42
+ reset_multi_statement = if batch && !multi_statements_enabled?
43
+ raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
44
+ true
83
45
  end
84
46
 
85
- with_raw_connection do |conn|
86
- conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
47
+ # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
48
+ # made since we established the connection
49
+ raw_connection.query_options[:database_timezone] = default_timezone
87
50
 
88
- yield
89
- ensure
90
- conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
91
- end
92
- end
51
+ result = nil
52
+ if binds.nil? || binds.empty?
53
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
54
+ result = raw_connection.query(sql)
55
+ # Ref: https://github.com/brianmario/mysql2/pull/1383
56
+ # As of mysql2 0.5.6 `#affected_rows` might raise Mysql2::Error if a prepared statement
57
+ # from that same connection was GCed while `#query` released the GVL.
58
+ # By avoiding to call `#affected_rows` when we have a result, we reduce the likeliness
59
+ # of hitting the bug.
60
+ @affected_rows_before_warnings = result&.size || raw_connection.affected_rows
61
+ end
62
+ elsif prepare
63
+ stmt = @statements[sql] ||= raw_connection.prepare(sql)
64
+ begin
65
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
66
+ result = stmt.execute(*type_casted_binds)
67
+ @affected_rows_before_warnings = stmt.affected_rows
68
+ end
69
+ rescue ::Mysql2::Error
70
+ @statements.delete(sql)
71
+ raise
72
+ end
73
+ else
74
+ stmt = raw_connection.prepare(sql)
75
+
76
+ begin
77
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
78
+ result = stmt.execute(*type_casted_binds)
79
+ @affected_rows_before_warnings = stmt.affected_rows
80
+ end
93
81
 
94
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
95
- log(sql, name, async: async) do |notification_payload|
96
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
97
- sync_timezone_changes(conn)
98
- result = conn.query(sql)
99
- conn.abandon_results!
100
- verified!
101
- handle_warnings(sql)
102
- notification_payload[:row_count] = result&.size || 0
103
- result
82
+ # Ref: https://github.com/brianmario/mysql2/pull/1383
83
+ # by eagerly closing uncached prepared statements, we also reduce the chances of
84
+ # that bug happening. It can still happen if `#execute` is used as we have no callback
85
+ # to eagerly close the statement.
86
+ if result
87
+ result.instance_variable_set(:@_ar_stmt_to_close, stmt)
88
+ else
89
+ stmt.close
90
+ end
91
+ rescue ::Mysql2::Error
92
+ stmt.close
93
+ raise
104
94
  end
105
95
  end
96
+
97
+ notification_payload[:affected_rows] = @affected_rows_before_warnings
98
+ notification_payload[:row_count] = result&.size || 0
99
+
100
+ raw_connection.abandon_results!
101
+
102
+ verified!
103
+ handle_warnings(sql)
104
+ result
105
+ ensure
106
+ if reset_multi_statement && active?
107
+ raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
108
+ end
106
109
  end
107
110
 
108
- def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false, allow_retry: false)
109
- sql = transform_query(sql)
110
- check_if_write_query(sql)
111
+ def cast_result(raw_result)
112
+ return ActiveRecord::Result.empty if raw_result.nil?
111
113
 
112
- mark_transaction_written_if_write(sql)
114
+ fields = raw_result.fields
113
115
 
114
- type_casted_binds = type_casted_binds(binds)
116
+ result = if fields.empty?
117
+ ActiveRecord::Result.empty
118
+ else
119
+ ActiveRecord::Result.new(fields, raw_result.to_a)
120
+ end
115
121
 
116
- log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
117
- with_raw_connection(allow_retry: allow_retry) do |conn|
118
- sync_timezone_changes(conn)
122
+ free_raw_result(raw_result)
119
123
 
120
- if cache_stmt
121
- stmt = @statements[sql] ||= conn.prepare(sql)
122
- else
123
- stmt = conn.prepare(sql)
124
- end
124
+ result
125
+ end
125
126
 
126
- begin
127
- result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
128
- stmt.execute(*type_casted_binds)
129
- end
130
- verified!
131
- result
132
- rescue ::Mysql2::Error => e
133
- if cache_stmt
134
- @statements.delete(sql)
135
- else
136
- stmt.close
137
- end
138
- raise e
139
- end
127
+ def affected_rows(raw_result)
128
+ free_raw_result(raw_result) if raw_result
140
129
 
141
- ret = yield stmt, result
142
- notification_payload[:row_count] = result&.size || 0
143
- result.free if result
144
- stmt.close unless cache_stmt
145
- ret
146
- end
130
+ @affected_rows_before_warnings
131
+ end
132
+
133
+ def free_raw_result(raw_result)
134
+ raw_result.free
135
+ if stmt = raw_result.instance_variable_get(:@_ar_stmt_to_close)
136
+ stmt.close
147
137
  end
148
138
  end
149
139
  end
@@ -55,6 +55,7 @@ module ActiveRecord
55
55
  def initialize(...)
56
56
  super
57
57
 
58
+ @affected_rows_before_warnings = nil
58
59
  @config[:flags] ||= 0
59
60
 
60
61
  if @config[:flags].kind_of? Array
@@ -92,14 +93,6 @@ module ActiveRecord
92
93
 
93
94
  # HELPER METHODS ===========================================
94
95
 
95
- def each_hash(result, &block) # :nodoc:
96
- if block_given?
97
- result.each(as: :hash, symbolize_keys: true, &block)
98
- else
99
- to_enum(:each_hash, result)
100
- end
101
- end
102
-
103
96
  def error_number(exception)
104
97
  exception.error_number if exception.respond_to?(:error_number)
105
98
  end
@@ -5,9 +5,8 @@ module ActiveRecord
5
5
  class PoolConfig # :nodoc:
6
6
  include MonitorMixin
7
7
 
8
- attr_reader :db_config, :role, :shard
8
+ attr_reader :db_config, :role, :shard, :connection_descriptor
9
9
  attr_writer :schema_reflection, :server_version
10
- attr_accessor :connection_class
11
10
 
12
11
  def schema_reflection
13
12
  @schema_reflection ||= SchemaReflection.new(db_config.lazy_schema_cache_path)
@@ -29,7 +28,7 @@ module ActiveRecord
29
28
  def initialize(connection_class, db_config, role, shard)
30
29
  super()
31
30
  @server_version = nil
32
- @connection_class = connection_class
31
+ self.connection_descriptor = connection_class
33
32
  @db_config = db_config
34
33
  @role = role
35
34
  @shard = shard
@@ -41,11 +40,12 @@ module ActiveRecord
41
40
  @server_version || synchronize { @server_version ||= connection.get_database_version }
42
41
  end
43
42
 
44
- def connection_name
45
- if connection_class.primary_class?
46
- "ActiveRecord::Base"
43
+ def connection_descriptor=(connection_descriptor)
44
+ case connection_descriptor
45
+ when ConnectionHandler::ConnectionDescriptor
46
+ @connection_descriptor = connection_descriptor
47
47
  else
48
- connection_class.name
48
+ @connection_descriptor = ConnectionHandler::ConnectionDescriptor.new(connection_descriptor.name, connection_descriptor.primary_class?)
49
49
  end
50
50
  end
51
51
 
@@ -12,16 +12,8 @@ module ActiveRecord
12
12
 
13
13
  # Queries the database and returns the results in an Array-like object
14
14
  def query(sql, name = nil) # :nodoc:
15
- mark_transaction_written_if_write(sql)
16
-
17
- log(sql, name) do |notification_payload|
18
- with_raw_connection do |conn|
19
- result = conn.async_exec(sql).map_types!(@type_map_for_results).values
20
- verified!
21
- notification_payload[:row_count] = result.count
22
- result
23
- end
24
- end
15
+ result = internal_execute(sql, name)
16
+ result.map_types!(@type_map_for_results).values
25
17
  end
26
18
 
27
19
  READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
@@ -50,36 +42,6 @@ module ActiveRecord
50
42
  @notice_receiver_sql_warnings = []
51
43
  end
52
44
 
53
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
54
- log(sql, name, async: async) do |notification_payload|
55
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
56
- result = conn.async_exec(sql)
57
- verified!
58
- handle_warnings(result)
59
- notification_payload[:row_count] = result.count
60
- result
61
- end
62
- end
63
- end
64
-
65
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
66
- execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
67
- types = {}
68
- fields = result.fields
69
- fields.each_with_index do |fname, i|
70
- ftype = result.ftype i
71
- fmod = result.fmod i
72
- types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
73
- end
74
- build_result(columns: fields, rows: result.values, column_types: types.freeze)
75
- end
76
- end
77
-
78
- def exec_delete(sql, name = nil, binds = []) # :nodoc:
79
- execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
80
- end
81
- alias :exec_update :exec_delete
82
-
83
45
  def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
84
46
  if use_insert_returning? || pk == false
85
47
  super
@@ -165,13 +127,80 @@ module ActiveRecord
165
127
  def cancel_any_running_query
166
128
  return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
167
129
 
168
- @raw_connection.cancel
130
+ # Skip @raw_connection.cancel (PG::Connection#cancel) when using libpq >= 18 with pg < 1.6.0,
131
+ # because the pg gem cannot obtain the backend_key in that case.
132
+ # This method is only called from exec_rollback_db_transaction and exec_restart_db_transaction.
133
+ # Even without cancel, rollback will still run. However, since any running
134
+ # query must finish first, the rollback may take longer.
135
+ if !(PG.library_version >= 18_00_00 && Gem::Version.new(PG::VERSION) < Gem::Version.new("1.6.0"))
136
+ @raw_connection.cancel
137
+ end
169
138
  @raw_connection.block
170
139
  rescue PG::Error
171
140
  end
172
141
 
173
- def execute_batch(statements, name = nil)
174
- execute(combine_multi_statements(statements))
142
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
143
+ update_typemap_for_default_timezone
144
+ result = if prepare
145
+ begin
146
+ stmt_key = prepare_statement(sql, binds, raw_connection)
147
+ notification_payload[:statement_name] = stmt_key
148
+ raw_connection.exec_prepared(stmt_key, type_casted_binds)
149
+ rescue PG::FeatureNotSupported => error
150
+ if is_cached_plan_failure?(error)
151
+ # Nothing we can do if we are in a transaction because all commands
152
+ # will raise InFailedSQLTransaction
153
+ if in_transaction?
154
+ raise PreparedStatementCacheExpired.new(error.message, connection_pool: @pool)
155
+ else
156
+ @lock.synchronize do
157
+ # outside of transactions we can simply flush this query and retry
158
+ @statements.delete sql_key(sql)
159
+ end
160
+ retry
161
+ end
162
+ end
163
+
164
+ raise
165
+ end
166
+ elsif binds.nil? || binds.empty?
167
+ raw_connection.async_exec(sql)
168
+ else
169
+ raw_connection.exec_params(sql, type_casted_binds)
170
+ end
171
+
172
+ verified!
173
+ handle_warnings(result)
174
+ notification_payload[:row_count] = result.ntuples
175
+ result
176
+ end
177
+
178
+ def cast_result(result)
179
+ if result.fields.empty?
180
+ result.clear
181
+ return ActiveRecord::Result.empty
182
+ end
183
+
184
+ types = {}
185
+ fields = result.fields
186
+ fields.each_with_index do |fname, i|
187
+ ftype = result.ftype i
188
+ fmod = result.fmod i
189
+ types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
190
+ end
191
+ ar_result = ActiveRecord::Result.new(fields, result.values, types.freeze)
192
+ result.clear
193
+ ar_result
194
+ end
195
+
196
+ def affected_rows(result)
197
+ affected_rows = result.cmd_tuples
198
+ result.clear
199
+ affected_rows
200
+ end
201
+
202
+ def execute_batch(statements, name = nil, **kwargs)
203
+ raw_execute(combine_multi_statements(statements), name, batch: true, **kwargs)
175
204
  end
176
205
 
177
206
  def build_truncate_statements(table_names)
@@ -65,7 +65,7 @@ module ActiveRecord
65
65
  end
66
66
 
67
67
  def map(value, &block)
68
- value.map { |v| subtype.map(v, &block) }
68
+ value.is_a?(::Array) ? value.map(&block) : subtype.map(value, &block)
69
69
  end
70
70
 
71
71
  def changed_in_place?(raw_old_value, new_value)
@@ -25,6 +25,10 @@ module ActiveRecord
25
25
  build_point(x, y)
26
26
  when ::Array
27
27
  build_point(*value)
28
+ when ::Hash
29
+ return if value.blank?
30
+
31
+ build_point(*values_array_from_hash(value))
28
32
  else
29
33
  value
30
34
  end
@@ -36,6 +40,8 @@ module ActiveRecord
36
40
  "(#{number_for_point(value.x)},#{number_for_point(value.y)})"
37
41
  when ::Array
38
42
  serialize(build_point(*value))
43
+ when ::Hash
44
+ serialize(build_point(*values_array_from_hash(value)))
39
45
  else
40
46
  super
41
47
  end
@@ -57,6 +63,10 @@ module ActiveRecord
57
63
  def build_point(x, y)
58
64
  ActiveRecord::Point.new(Float(x), Float(y))
59
65
  end
66
+
67
+ def values_array_from_hash(value)
68
+ [value.values_at(:x, "x").compact.first, value.values_at(:y, "y").compact.first]
69
+ end
60
70
  end
61
71
  end
62
72
  end
@@ -45,12 +45,10 @@ Rails needs superuser privileges to disable referential integrity.
45
45
  BEGIN
46
46
  FOR r IN (
47
47
  SELECT FORMAT(
48
- 'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I'' AND connamespace::regnamespace = ''%I''::regnamespace; ALTER TABLE %I.%I VALIDATE CONSTRAINT %I;',
48
+ 'UPDATE pg_catalog.pg_constraint SET convalidated=false WHERE conname = ''%1$I'' AND connamespace::regnamespace = ''%2$I''::regnamespace; ALTER TABLE %2$I.%3$I VALIDATE CONSTRAINT %1$I;',
49
49
  constraint_name,
50
50
  table_schema,
51
- table_schema,
52
- table_name,
53
- constraint_name
51
+ table_name
54
52
  ) AS constraint_check
55
53
  FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'
56
54
  )
@@ -11,14 +11,11 @@ module ActiveRecord
11
11
  sql = super
12
12
  sql << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
13
13
  sql << o.exclusion_constraint_adds.map { |con| visit_AddExclusionConstraint con }.join(" ")
14
- sql << o.exclusion_constraint_drops.map { |con| visit_DropExclusionConstraint con }.join(" ")
15
14
  sql << o.unique_constraint_adds.map { |con| visit_AddUniqueConstraint con }.join(" ")
16
- sql << o.unique_constraint_drops.map { |con| visit_DropUniqueConstraint con }.join(" ")
17
15
  end
18
16
 
19
17
  def visit_AddForeignKey(o)
20
18
  super.dup.tap do |sql|
21
- sql << " DEFERRABLE INITIALLY #{o.options[:deferrable].to_s.upcase}" if o.deferrable
22
19
  sql << " NOT VALID" unless o.validate?
23
20
  end
24
21
  end
@@ -55,6 +52,7 @@ module ActiveRecord
55
52
  sql = ["CONSTRAINT"]
56
53
  sql << quote_column_name(o.name)
57
54
  sql << "UNIQUE"
55
+ sql << "NULLS NOT DISTINCT" if supports_nulls_not_distinct? && o.nulls_not_distinct
58
56
 
59
57
  if o.using_index
60
58
  sql << "USING INDEX #{quote_column_name(o.using_index)}"
@@ -73,18 +71,10 @@ module ActiveRecord
73
71
  "ADD #{accept(o)}"
74
72
  end
75
73
 
76
- def visit_DropExclusionConstraint(name)
77
- "DROP CONSTRAINT #{quote_column_name(name)}"
78
- end
79
-
80
74
  def visit_AddUniqueConstraint(o)
81
75
  "ADD #{accept(o)}"
82
76
  end
83
77
 
84
- def visit_DropUniqueConstraint(name)
85
- "DROP CONSTRAINT #{quote_column_name(name)}"
86
- end
87
-
88
78
  def visit_ChangeColumnDefinition(o)
89
79
  column = o.column
90
80
  column.sql_type = type_to_sql(column.type, **column.options)
@@ -224,6 +224,10 @@ module ActiveRecord
224
224
  options[:using_index]
225
225
  end
226
226
 
227
+ def nulls_not_distinct
228
+ options[:nulls_not_distinct]
229
+ end
230
+
227
231
  def export_name_on_schema_dump?
228
232
  !ActiveRecord::SchemaDumper.unique_ignore_pattern.match?(name) if name
229
233
  end
@@ -319,7 +323,7 @@ module ActiveRecord
319
323
 
320
324
  # Adds a unique constraint.
321
325
  #
322
- # t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred)
326
+ # t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred, nulls_not_distinct: true)
323
327
  #
324
328
  # See {connection.add_unique_constraint}[rdoc-ref:SchemaStatements#add_unique_constraint]
325
329
  def unique_constraint(...)
@@ -358,15 +362,13 @@ module ActiveRecord
358
362
 
359
363
  # = Active Record PostgreSQL Adapter Alter \Table
360
364
  class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
361
- attr_reader :constraint_validations, :exclusion_constraint_adds, :exclusion_constraint_drops, :unique_constraint_adds, :unique_constraint_drops
365
+ attr_reader :constraint_validations, :exclusion_constraint_adds, :unique_constraint_adds
362
366
 
363
367
  def initialize(td)
364
368
  super
365
369
  @constraint_validations = []
366
370
  @exclusion_constraint_adds = []
367
- @exclusion_constraint_drops = []
368
371
  @unique_constraint_adds = []
369
- @unique_constraint_drops = []
370
372
  end
371
373
 
372
374
  def validate_constraint(name)
@@ -377,17 +379,9 @@ module ActiveRecord
377
379
  @exclusion_constraint_adds << @td.new_exclusion_constraint_definition(expression, options)
378
380
  end
379
381
 
380
- def drop_exclusion_constraint(constraint_name)
381
- @exclusion_constraint_drops << constraint_name
382
- end
383
-
384
382
  def add_unique_constraint(column_name, options)
385
383
  @unique_constraint_adds << @td.new_unique_constraint_definition(column_name, options)
386
384
  end
387
-
388
- def drop_unique_constraint(unique_constraint_name)
389
- @unique_constraint_drops << unique_constraint_name
390
- end
391
385
  end
392
386
  end
393
387
  end
@@ -68,6 +68,7 @@ module ActiveRecord
68
68
  "t.unique_constraint #{unique_constraint.column.inspect}"
69
69
  ]
70
70
 
71
+ parts << "nulls_not_distinct: #{unique_constraint.nulls_not_distinct.inspect}" if unique_constraint.nulls_not_distinct
71
72
  parts << "deferrable: #{unique_constraint.deferrable.inspect}" if unique_constraint.deferrable
72
73
 
73
74
  if unique_constraint.export_name_on_schema_dump?
@@ -91,7 +92,7 @@ module ActiveRecord
91
92
  spec = { type: schema_type(column).inspect }.merge!(spec)
92
93
  end
93
94
 
94
- spec[:enum_type] = "\"#{column.sql_type}\"" if column.enum?
95
+ spec[:enum_type] = column.sql_type.inspect if column.enum?
95
96
 
96
97
  spec
97
98
  end