activerecord 7.0.0.alpha2 → 7.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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +539 -11
  3. data/lib/active_record/associations/association.rb +2 -8
  4. data/lib/active_record/associations/builder/collection_association.rb +9 -2
  5. data/lib/active_record/associations/collection_association.rb +10 -2
  6. data/lib/active_record/associations/join_dependency.rb +6 -2
  7. data/lib/active_record/associations/preloader/association.rb +68 -48
  8. data/lib/active_record/associations/preloader/batch.rb +3 -6
  9. data/lib/active_record/associations/preloader/through_association.rb +19 -9
  10. data/lib/active_record/associations/preloader.rb +14 -24
  11. data/lib/active_record/associations/through_association.rb +2 -2
  12. data/lib/active_record/associations.rb +16 -3
  13. data/lib/active_record/asynchronous_queries_tracker.rb +3 -0
  14. data/lib/active_record/attribute_methods/dirty.rb +9 -1
  15. data/lib/active_record/attribute_methods.rb +7 -5
  16. data/lib/active_record/autosave_association.rb +3 -3
  17. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +6 -26
  18. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -2
  19. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +18 -5
  20. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +33 -70
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +9 -2
  25. data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -19
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +37 -8
  27. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1 -0
  28. data/lib/active_record/connection_adapters/column.rb +4 -0
  29. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -2
  30. data/lib/active_record/connection_adapters/mysql/quoting.rb +23 -24
  31. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -1
  32. data/lib/active_record/connection_adapters/pool_config.rb +7 -5
  33. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  34. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -0
  35. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -44
  36. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  37. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +18 -1
  38. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  39. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -4
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +48 -5
  41. data/lib/active_record/connection_adapters/schema_cache.rb +3 -1
  42. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +4 -2
  43. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +15 -16
  44. data/lib/active_record/connection_handling.rb +31 -19
  45. data/lib/active_record/core.rb +13 -24
  46. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  47. data/lib/active_record/database_configurations/database_config.rb +0 -9
  48. data/lib/active_record/database_configurations/hash_config.rb +40 -8
  49. data/lib/active_record/database_configurations.rb +2 -27
  50. data/lib/active_record/delegated_type.rb +19 -0
  51. data/lib/active_record/encryption/encryptable_record.rb +1 -1
  52. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  53. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +1 -2
  54. data/lib/active_record/encryption/message_serializer.rb +11 -1
  55. data/lib/active_record/encryption/scheme.rb +1 -1
  56. data/lib/active_record/enum.rb +8 -1
  57. data/lib/active_record/errors.rb +1 -1
  58. data/lib/active_record/explain_registry.rb +11 -6
  59. data/lib/active_record/fixture_set/table_row.rb +1 -1
  60. data/lib/active_record/fixtures.rb +1 -9
  61. data/lib/active_record/future_result.rb +2 -2
  62. data/lib/active_record/gem_version.rb +1 -1
  63. data/lib/active_record/insert_all.rb +52 -15
  64. data/lib/active_record/integration.rb +3 -2
  65. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  66. data/lib/active_record/locking/pessimistic.rb +9 -3
  67. data/lib/active_record/log_subscriber.rb +8 -1
  68. data/lib/active_record/middleware/shard_selector.rb +60 -0
  69. data/lib/active_record/migration.rb +2 -2
  70. data/lib/active_record/model_schema.rb +1 -28
  71. data/lib/active_record/nested_attributes.rb +11 -10
  72. data/lib/active_record/no_touching.rb +1 -1
  73. data/lib/active_record/persistence.rb +99 -21
  74. data/lib/active_record/query_logs.rb +18 -83
  75. data/lib/active_record/railtie.rb +11 -1
  76. data/lib/active_record/railties/databases.rake +4 -91
  77. data/lib/active_record/reflection.rb +22 -6
  78. data/lib/active_record/relation/calculations.rb +1 -10
  79. data/lib/active_record/relation/finder_methods.rb +0 -13
  80. data/lib/active_record/relation/query_methods.rb +5 -14
  81. data/lib/active_record/relation/record_fetch_warning.rb +5 -7
  82. data/lib/active_record/relation/where_clause.rb +2 -15
  83. data/lib/active_record/relation.rb +11 -15
  84. data/lib/active_record/result.rb +0 -5
  85. data/lib/active_record/runtime_registry.rb +10 -12
  86. data/lib/active_record/schema_dumper.rb +7 -0
  87. data/lib/active_record/schema_migration.rb +4 -0
  88. data/lib/active_record/scoping.rb +34 -22
  89. data/lib/active_record/suppressor.rb +11 -15
  90. data/lib/active_record/tasks/database_tasks.rb +18 -44
  91. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -1
  92. data/lib/active_record/validations/uniqueness.rb +1 -1
  93. data/lib/active_record.rb +41 -33
  94. data/lib/arel/crud.rb +12 -2
  95. data/lib/arel/delete_manager.rb +16 -0
  96. data/lib/arel/filter_predications.rb +9 -0
  97. data/lib/arel/nodes/delete_statement.rb +5 -1
  98. data/lib/arel/nodes/filter.rb +10 -0
  99. data/lib/arel/nodes/function.rb +1 -0
  100. data/lib/arel/nodes/update_statement.rb +5 -1
  101. data/lib/arel/nodes.rb +1 -0
  102. data/lib/arel/predications.rb +10 -2
  103. data/lib/arel/update_manager.rb +16 -0
  104. data/lib/arel/visitors/mysql.rb +2 -1
  105. data/lib/arel/visitors/to_sql.rb +15 -0
  106. data/lib/arel.rb +1 -0
  107. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  108. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  109. metadata +18 -12
@@ -16,6 +16,31 @@ module ActiveRecord
16
16
  @connection.unescape_bytea(value) if value
17
17
  end
18
18
 
19
+ def quote(value) # :nodoc:
20
+ case value
21
+ when OID::Xml::Data
22
+ "xml '#{quote_string(value.to_s)}'"
23
+ when OID::Bit::Data
24
+ if value.binary?
25
+ "B'#{value}'"
26
+ elsif value.hex?
27
+ "X'#{value}'"
28
+ end
29
+ when Numeric
30
+ if value.finite?
31
+ super
32
+ else
33
+ "'#{value}'"
34
+ end
35
+ when OID::Array::Data
36
+ quote(encode_array(value))
37
+ when Range
38
+ quote(encode_range(value))
39
+ else
40
+ super
41
+ end
42
+ end
43
+
19
44
  # Quotes strings for use in SQL input.
20
45
  def quote_string(s) # :nodoc:
21
46
  PG::Connection.escape(s)
@@ -74,6 +99,24 @@ module ActiveRecord
74
99
  end
75
100
  end
76
101
 
102
+ def type_cast(value) # :nodoc:
103
+ case value
104
+ when Type::Binary::Data
105
+ # Return a bind param hash with format as binary.
106
+ # See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc
107
+ # for more information
108
+ { value: value.to_s, format: 1 }
109
+ when OID::Xml::Data, OID::Bit::Data
110
+ value.to_s
111
+ when OID::Array::Data
112
+ encode_array(value)
113
+ when Range
114
+ encode_range(value)
115
+ else
116
+ super
117
+ end
118
+ end
119
+
77
120
  def lookup_cast_type_from_column(column) # :nodoc:
78
121
  type_map.lookup(column.oid, column.fmod, column.sql_type)
79
122
  end
@@ -120,49 +163,6 @@ module ActiveRecord
120
163
  super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
121
164
  end
122
165
 
123
- def _quote(value)
124
- case value
125
- when OID::Xml::Data
126
- "xml '#{quote_string(value.to_s)}'"
127
- when OID::Bit::Data
128
- if value.binary?
129
- "B'#{value}'"
130
- elsif value.hex?
131
- "X'#{value}'"
132
- end
133
- when Numeric
134
- if value.finite?
135
- super
136
- else
137
- "'#{value}'"
138
- end
139
- when OID::Array::Data
140
- _quote(encode_array(value))
141
- when Range
142
- _quote(encode_range(value))
143
- else
144
- super
145
- end
146
- end
147
-
148
- def _type_cast(value)
149
- case value
150
- when Type::Binary::Data
151
- # Return a bind param hash with format as binary.
152
- # See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc
153
- # for more information
154
- { value: value.to_s, format: 1 }
155
- when OID::Xml::Data, OID::Bit::Data
156
- value.to_s
157
- when OID::Array::Data
158
- encode_array(value)
159
- when Range
160
- encode_range(value)
161
- else
162
- super
163
- end
164
- end
165
-
166
166
  def encode_array(array_data)
167
167
  encoder = array_data.encoder
168
168
  values = type_cast_array(array_data.values)
@@ -188,7 +188,7 @@ module ActiveRecord
188
188
  def type_cast_array(values)
189
189
  case values
190
190
  when ::Array then values.map { |item| type_cast_array(item) }
191
- else _type_cast(values)
191
+ else type_cast(values)
192
192
  end
193
193
  end
194
194
 
@@ -10,7 +10,14 @@ module ActiveRecord
10
10
  end
11
11
 
12
12
  def visit_AddForeignKey(o)
13
- super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
13
+ super.dup.tap do |sql|
14
+ if o.deferrable
15
+ sql << " DEFERRABLE"
16
+ sql << " INITIALLY #{o.deferrable.to_s.upcase}" unless o.deferrable == true
17
+ end
18
+
19
+ sql << " NOT VALID" unless o.validate?
20
+ end
14
21
  end
15
22
 
16
23
  def visit_CheckConstraintDefinition(o)
@@ -61,6 +68,19 @@ module ActiveRecord
61
68
  if options[:collation]
62
69
  sql << " COLLATE \"#{options[:collation]}\""
63
70
  end
71
+
72
+ if as = options[:as]
73
+ sql << " GENERATED ALWAYS AS (#{as})"
74
+
75
+ if options[:stored]
76
+ sql << " STORED"
77
+ else
78
+ raise ArgumentError, <<~MSG
79
+ PostgreSQL currently does not support VIRTUAL (not persisted) generated columns.
80
+ Specify 'stored: true' option for '#{options[:column].name}'
81
+ MSG
82
+ end
83
+ end
64
84
  super
65
85
  end
66
86
 
@@ -173,11 +173,19 @@ module ActiveRecord
173
173
  # :method: xml
174
174
  # :call-seq: xml(*names, **options)
175
175
 
176
+ ##
177
+ # :method: timestamptz
178
+ # :call-seq: timestamptz(*names, **options)
179
+
180
+ ##
181
+ # :method: enum
182
+ # :call-seq: enum(*names, **options)
183
+
176
184
  included do
177
185
  define_column_methods :bigserial, :bit, :bit_varying, :cidr, :citext, :daterange,
178
186
  :hstore, :inet, :interval, :int4range, :int8range, :jsonb, :ltree, :macaddr,
179
187
  :money, :numrange, :oid, :point, :line, :lseg, :box, :path, :polygon, :circle,
180
- :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml, :timestamptz
188
+ :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml, :timestamptz, :enum
181
189
  end
182
190
  end
183
191
 
@@ -191,6 +199,15 @@ module ActiveRecord
191
199
  @unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables
192
200
  end
193
201
 
202
+ def new_column_definition(name, type, **options) # :nodoc:
203
+ case type
204
+ when :virtual
205
+ type = options[:type]
206
+ end
207
+
208
+ super
209
+ end
210
+
194
211
  private
195
212
  def aliased_types(name, fallback)
196
213
  fallback
@@ -16,9 +16,30 @@ module ActiveRecord
16
16
  end
17
17
  end
18
18
 
19
+ def types(stream)
20
+ types = @connection.enum_types
21
+ if types.any?
22
+ stream.puts " # Custom types defined in this database."
23
+ stream.puts " # Note that some types may not work with other database engines. Be careful if changing database."
24
+ types.sort.each do |name, values|
25
+ stream.puts " create_enum #{name.inspect}, #{values.split(",").inspect}"
26
+ end
27
+ stream.puts
28
+ end
29
+ end
30
+
19
31
  def prepare_column_options(column)
20
32
  spec = super
21
33
  spec[:array] = "true" if column.array?
34
+
35
+ if @connection.supports_virtual_columns? && column.virtual?
36
+ spec[:as] = extract_expression_for_virtual_column(column)
37
+ spec[:stored] = true
38
+ spec = { type: schema_type(column).inspect }.merge!(spec)
39
+ end
40
+
41
+ spec[:enum_type] = "\"#{column.sql_type}\"" if column.enum?
42
+
22
43
  spec
23
44
  end
24
45
 
@@ -43,6 +64,10 @@ module ActiveRecord
43
64
  def schema_expression(column)
44
65
  super unless column.serial?
45
66
  end
67
+
68
+ def extract_expression_for_virtual_column(column)
69
+ column.default_function.inspect
70
+ end
46
71
  end
47
72
  end
48
73
  end
@@ -483,7 +483,7 @@ module ActiveRecord
483
483
  def foreign_keys(table_name)
484
484
  scope = quoted_scope(table_name)
485
485
  fk_info = exec_query(<<~SQL, "SCHEMA")
486
- SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid
486
+ SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred
487
487
  FROM pg_constraint c
488
488
  JOIN pg_class t1 ON c.conrelid = t1.oid
489
489
  JOIN pg_class t2 ON c.confrelid = t2.oid
@@ -505,6 +505,8 @@ module ActiveRecord
505
505
 
506
506
  options[:on_delete] = extract_foreign_key_action(row["on_delete"])
507
507
  options[:on_update] = extract_foreign_key_action(row["on_update"])
508
+ options[:deferrable] = extract_foreign_key_deferrable(row["deferrable"], row["deferred"])
509
+
508
510
  options[:validate] = row["valid"]
509
511
 
510
512
  ForeignKeyDefinition.new(table_name, row["to_table"], options)
@@ -542,7 +544,7 @@ module ActiveRecord
542
544
  end
543
545
 
544
546
  # Maps logical Rails types to PostgreSQL-specific data types.
545
- def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
547
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, enum_type: nil, **) # :nodoc:
546
548
  sql = \
547
549
  case type.to_s
548
550
  when "binary"
@@ -566,6 +568,10 @@ module ActiveRecord
566
568
  when 5..8; "bigint"
567
569
  else raise ArgumentError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead."
568
570
  end
571
+ when "enum"
572
+ raise ArgumentError, "enum_type is required for enums" if enum_type.nil?
573
+
574
+ enum_type
569
575
  else
570
576
  super
571
577
  end
@@ -654,7 +660,7 @@ module ActiveRecord
654
660
  end
655
661
 
656
662
  def new_column_from_field(table_name, field)
657
- column_name, type, default, notnull, oid, fmod, collation, comment = field
663
+ column_name, type, default, notnull, oid, fmod, collation, comment, attgenerated = field
658
664
  type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
659
665
  default_value = extract_value_from_default(default)
660
666
  default_function = extract_default_function(default_value, default)
@@ -671,7 +677,8 @@ module ActiveRecord
671
677
  default_function,
672
678
  collation: collation,
673
679
  comment: comment.presence,
674
- serial: serial
680
+ serial: serial,
681
+ generated: attgenerated
675
682
  )
676
683
  end
677
684
 
@@ -711,6 +718,10 @@ module ActiveRecord
711
718
  end
712
719
  end
713
720
 
721
+ def extract_foreign_key_deferrable(deferrable, deferred)
722
+ deferrable && (deferred ? :deferred : true)
723
+ end
724
+
714
725
  def add_column_for_alter(table_name, column_name, type, **options)
715
726
  return super unless options.key?(:comment)
716
727
  [super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
@@ -125,6 +125,7 @@ module ActiveRecord
125
125
  string: { name: "character varying" },
126
126
  text: { name: "text" },
127
127
  integer: { name: "integer", limit: 4 },
128
+ bigint: { name: "bigint" },
128
129
  float: { name: "float" },
129
130
  decimal: { name: "decimal" },
130
131
  datetime: {}, # set dynamically based on datetime_type
@@ -163,6 +164,7 @@ module ActiveRecord
163
164
  money: { name: "money" },
164
165
  interval: { name: "interval" },
165
166
  oid: { name: "oid" },
167
+ enum: {} # special type https://www.postgresql.org/docs/current/datatype-enum.html
166
168
  }
167
169
 
168
170
  OID = PostgreSQL::OID # :nodoc:
@@ -181,7 +183,7 @@ module ActiveRecord
181
183
  end
182
184
 
183
185
  def supports_partitioned_indexes?
184
- database_version >= 110_000
186
+ database_version >= 110_000 # >= 11.0
185
187
  end
186
188
 
187
189
  def supports_partial_index?
@@ -208,6 +210,10 @@ module ActiveRecord
208
210
  true
209
211
  end
210
212
 
213
+ def supports_deferrable_constraints?
214
+ true
215
+ end
216
+
211
217
  def supports_views?
212
218
  true
213
219
  end
@@ -233,12 +239,16 @@ module ActiveRecord
233
239
  end
234
240
 
235
241
  def supports_insert_on_conflict?
236
- database_version >= 90500
242
+ database_version >= 90500 # >= 9.5
237
243
  end
238
244
  alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
239
245
  alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
240
246
  alias supports_insert_conflict_target? supports_insert_on_conflict?
241
247
 
248
+ def supports_virtual_columns?
249
+ database_version >= 120_000 # >= 12.0
250
+ end
251
+
242
252
  def index_algorithms
243
253
  { concurrently: "CONCURRENTLY" }
244
254
  end
@@ -388,7 +398,7 @@ module ActiveRecord
388
398
  end
389
399
 
390
400
  def supports_pgcrypto_uuid?
391
- database_version >= 90400
401
+ database_version >= 90400 # >= 9.4
392
402
  end
393
403
 
394
404
  def supports_optimizer_hints?
@@ -444,6 +454,38 @@ module ActiveRecord
444
454
  exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
445
455
  end
446
456
 
457
+ # Returns a list of defined enum types, and their values.
458
+ def enum_types
459
+ query = <<~SQL
460
+ SELECT
461
+ type.typname AS name,
462
+ string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
463
+ FROM pg_enum AS enum
464
+ JOIN pg_type AS type
465
+ ON (type.oid = enum.enumtypid)
466
+ GROUP BY type.typname;
467
+ SQL
468
+ exec_query(query, "SCHEMA").cast_values
469
+ end
470
+
471
+ # Given a name and an array of values, creates an enum type.
472
+ def create_enum(name, values)
473
+ sql_values = values.map { |s| "'#{s}'" }.join(", ")
474
+ query = <<~SQL
475
+ DO $$
476
+ BEGIN
477
+ IF NOT EXISTS (
478
+ SELECT 1 FROM pg_type t
479
+ WHERE t.typname = '#{name}'
480
+ ) THEN
481
+ CREATE TYPE \"#{name}\" AS ENUM (#{sql_values});
482
+ END IF;
483
+ END
484
+ $$;
485
+ SQL
486
+ exec_query(query)
487
+ end
488
+
447
489
  # Returns the configured supported identifier length supported by PostgreSQL
448
490
  def max_identifier_length
449
491
  @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
@@ -489,7 +531,7 @@ module ActiveRecord
489
531
  end
490
532
 
491
533
  def check_version # :nodoc:
492
- if database_version < 90300
534
+ if database_version < 90300 # < 9.3
493
535
  raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
494
536
  end
495
537
  end
@@ -874,7 +916,8 @@ module ActiveRecord
874
916
  query(<<~SQL, "SCHEMA")
875
917
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
876
918
  pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
877
- c.collname, col_description(a.attrelid, a.attnum) AS comment
919
+ c.collname, col_description(a.attrelid, a.attnum) AS comment,
920
+ #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
878
921
  FROM pg_attribute a
879
922
  LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
880
923
  LEFT JOIN pg_type t ON a.atttypid = t.oid
@@ -208,7 +208,7 @@ module ActiveRecord
208
208
  end
209
209
 
210
210
  def reset_version!
211
- @version = connection.migration_context.current_version
211
+ @version = connection.schema_version
212
212
  end
213
213
 
214
214
  def derive_columns_hash_and_deduplicate_values
@@ -239,6 +239,8 @@ module ActiveRecord
239
239
  end
240
240
 
241
241
  def open(filename)
242
+ FileUtils.mkdir_p(File.dirname(filename))
243
+
242
244
  File.atomic_write(filename) do |file|
243
245
  if File.extname(filename) == ".gz"
244
246
  zipper = Zlib::GzipWriter.new file
@@ -11,6 +11,8 @@ module ActiveRecord
11
11
 
12
12
  def write_query?(sql) # :nodoc:
13
13
  !READ_QUERY.match?(sql)
14
+ rescue ArgumentError # Invalid encoding
15
+ !READ_QUERY.match?(sql.b)
14
16
  end
15
17
 
16
18
  def explain(arel, binds = [])
@@ -78,7 +80,7 @@ module ActiveRecord
78
80
  raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
79
81
  raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
80
82
 
81
- Thread.current.thread_variable_set("read_uncommitted", @connection.get_first_value("PRAGMA read_uncommitted"))
83
+ ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted] = @connection.get_first_value("PRAGMA read_uncommitted")
82
84
  @connection.read_uncommitted = true
83
85
  begin_db_transaction
84
86
  end
@@ -108,7 +110,7 @@ module ActiveRecord
108
110
 
109
111
  private
110
112
  def reset_read_uncommitted
111
- read_uncommitted = Thread.current.thread_variable_get("read_uncommitted")
113
+ read_uncommitted = ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted]
112
114
  return unless read_uncommitted
113
115
 
114
116
  @connection.read_uncommitted = read_uncommitted
@@ -45,6 +45,21 @@ module ActiveRecord
45
45
  0
46
46
  end
47
47
 
48
+ def type_cast(value) # :nodoc:
49
+ case value
50
+ when BigDecimal
51
+ value.to_f
52
+ when String
53
+ if value.encoding == Encoding::ASCII_8BIT
54
+ super(value.encode(Encoding::UTF_8))
55
+ else
56
+ super
57
+ end
58
+ else
59
+ super
60
+ end
61
+ end
62
+
48
63
  def column_name_matcher
49
64
  COLUMN_NAME
50
65
  end
@@ -80,22 +95,6 @@ module ActiveRecord
80
95
  /ix
81
96
 
82
97
  private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
83
-
84
- private
85
- def _type_cast(value)
86
- case value
87
- when BigDecimal
88
- value.to_f
89
- when String
90
- if value.encoding == Encoding::ASCII_8BIT
91
- super(value.encode(Encoding::UTF_8))
92
- else
93
- super
94
- end
95
- else
96
- super
97
- end
98
- end
99
98
  end
100
99
  end
101
100
  end
@@ -153,10 +153,6 @@ module ActiveRecord
153
153
  raise ArgumentError, "must provide a `shard` and/or `role`."
154
154
  end
155
155
 
156
- unless role
157
- raise ArgumentError, "`connected_to` cannot accept a `shard` argument without a `role`."
158
- end
159
-
160
156
  with_role_and_shard(role, shard, prevent_writes, &blk)
161
157
  end
162
158
 
@@ -186,7 +182,7 @@ module ActiveRecord
186
182
 
187
183
  prevent_writes = true if role == ActiveRecord.reading_role
188
184
 
189
- connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes }
185
+ append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes)
190
186
  yield
191
187
  ensure
192
188
  connected_to_stack.pop
@@ -206,7 +202,26 @@ module ActiveRecord
206
202
 
207
203
  prevent_writes = true if role == ActiveRecord.reading_role
208
204
 
209
- self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] }
205
+ append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
206
+ end
207
+
208
+ # Prohibit swapping shards while inside of the passed block.
209
+ #
210
+ # In some cases you may want to be able to swap shards but not allow a
211
+ # nested call to connected_to or connected_to_many to swap again. This
212
+ # is useful in cases you're using sharding to provide per-request
213
+ # database isolation.
214
+ def prohibit_shard_swapping(enabled = true)
215
+ prev_value = ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping]
216
+ ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping] = enabled
217
+ yield
218
+ ensure
219
+ ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping] = prev_value
220
+ end
221
+
222
+ # Determine whether or not shard swapping is currently prohibited
223
+ def shard_swapping_prohibited?
224
+ ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping]
210
225
  end
211
226
 
212
227
  # Prevent writing to the database regardless of role.
@@ -279,17 +294,6 @@ module ActiveRecord
279
294
  self == Base || application_record_class?
280
295
  end
281
296
 
282
- # Returns the configuration of the associated connection as a hash:
283
- #
284
- # ActiveRecord::Base.connection_config
285
- # # => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}
286
- #
287
- # Please use only for reading.
288
- def connection_config
289
- connection_pool.db_config.configuration_hash
290
- end
291
- deprecate connection_config: "Use connection_db_config instead"
292
-
293
297
  # Returns the db_config object from the associated connection:
294
298
  #
295
299
  # ActiveRecord::Base.connection_db_config
@@ -361,12 +365,12 @@ module ActiveRecord
361
365
  if ActiveRecord.legacy_connection_handling
362
366
  with_handler(role.to_sym) do
363
367
  connection_handler.while_preventing_writes(prevent_writes) do
364
- self.connected_to_stack << { shard: shard, klasses: [self] }
368
+ append_to_connected_to_stack(shard: shard, klasses: [self])
365
369
  yield
366
370
  end
367
371
  end
368
372
  else
369
- self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] }
373
+ append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
370
374
  return_value = yield
371
375
  return_value.load if return_value.is_a? ActiveRecord::Relation
372
376
  return_value
@@ -375,6 +379,14 @@ module ActiveRecord
375
379
  self.connected_to_stack.pop
376
380
  end
377
381
 
382
+ def append_to_connected_to_stack(entry)
383
+ if shard_swapping_prohibited? && entry[:shard].present?
384
+ raise ArgumentError, "cannot swap `shard` while shard swapping is prohibited."
385
+ end
386
+
387
+ connected_to_stack << entry
388
+ end
389
+
378
390
  def swap_connection_handler(handler, &blk) # :nodoc:
379
391
  old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
380
392
  return_value = yield