activerecord7-redshift-adapter-pennylane 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,28 +23,14 @@ require 'ipaddr'
23
23
  ActiveRecord::Tasks::DatabaseTasks.register_task(/redshift/, 'ActiveRecord::Tasks::PostgreSQLDatabaseTasks')
24
24
  module ActiveRecord
25
25
  module ConnectionHandling # :nodoc:
26
- RS_VALID_CONN_PARAMS = %i[host hostaddr port dbname user password connect_timeout
27
- client_encoding options application_name fallback_application_name
28
- keepalives keepalives_idle keepalives_interval keepalives_count
29
- tty sslmode requiressl sslcompression sslcert sslkey
30
- sslrootcert sslcrl requirepeer krbsrvname gsslib service].freeze
26
+
27
+ def redshift_adapter_class
28
+ ConnectionAdapters::RedshiftAdapter
29
+ end
31
30
 
32
31
  # Establishes a connection to the database that's used by all Active Record objects
33
32
  def redshift_connection(config)
34
- conn_params = config.symbolize_keys
35
-
36
- conn_params.delete_if { |_, v| v.nil? }
37
-
38
- # Map ActiveRecords param names to PGs.
39
- conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
40
- conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
41
-
42
- # Forward only valid config params to PG::Connection.connect.
43
- conn_params.keep_if { |k, _| RS_VALID_CONN_PARAMS.include?(k) }
44
-
45
- # The postgres drivers don't allow the creation of an unconnected PG::Connection object,
46
- # so just pass a nil connection object for the time being.
47
- ConnectionAdapters::RedshiftAdapter.new(nil, logger, conn_params, config)
33
+ redshift_adapter_class.new(config)
48
34
  end
49
35
  end
50
36
 
@@ -78,6 +64,12 @@ module ActiveRecord
78
64
  class RedshiftAdapter < AbstractAdapter
79
65
  ADAPTER_NAME = 'Redshift'
80
66
 
67
+ RS_VALID_CONN_PARAMS = %i[host hostaddr port dbname user password connect_timeout
68
+ client_encoding options application_name fallback_application_name
69
+ keepalives keepalives_idle keepalives_interval keepalives_count
70
+ tty sslmode requiressl sslcompression sslcert sslkey
71
+ sslrootcert sslcrl requirepeer krbsrvname gsslib service].freeze
72
+
81
73
  NATIVE_DATABASE_TYPES = {
82
74
  primary_key: 'integer identity primary key',
83
75
  string: { name: 'varchar' },
@@ -143,54 +135,63 @@ module ActiveRecord
143
135
  end
144
136
 
145
137
  def next_key
146
- "a#{@counter + 1}"
147
- end
148
-
149
- def []=(sql, key)
150
- super.tap { @counter += 1 }
138
+ "a#{@counter += 1}"
151
139
  end
152
140
 
153
141
  private
154
142
 
155
143
  def dealloc(key)
156
- @raw_connection.query "DEALLOCATE #{key}" if connection_active?
144
+ # This is ugly, but safe: the statement pool is only
145
+ # accessed while holding the connection's lock. (And we
146
+ # don't need the complication of with_raw_connection because
147
+ # a reconnect would invalidate the entire statement pool.)
148
+ if conn = @connection.instance_variable_get(:@raw_connection)
149
+ conn.query "DEALLOCATE #{key}" if conn.status == PG::CONNECTION_OK
150
+ end
157
151
  rescue PG::Error
158
152
  end
153
+ end
159
154
 
160
- def connection_active?
161
- @raw_connection.status == PG::CONNECTION_OK
162
- rescue PG::Error
163
- false
155
+ class << self
156
+ def new_client(conn_params)
157
+ PG.connect(**conn_params)
158
+ rescue ::PG::Error => error
159
+ if conn_params && conn_params[:dbname] == "postgres"
160
+ raise ActiveRecord::ConnectionNotEstablished, error.message
161
+ elsif conn_params && conn_params[:dbname] && error.message.include?(conn_params[:dbname])
162
+ raise ActiveRecord::NoDatabaseError.db_error(conn_params[:dbname])
163
+ elsif conn_params && conn_params[:user] && error.message.include?(conn_params[:user])
164
+ raise ActiveRecord::DatabaseConnectionError.username_error(conn_params[:user])
165
+ elsif conn_params && conn_params[:host] && error.message.include?(conn_params[:host])
166
+ raise ActiveRecord::DatabaseConnectionError.hostname_error(conn_params[:host])
167
+ else
168
+ raise ActiveRecord::ConnectionNotEstablished, error.message
169
+ end
164
170
  end
165
171
  end
166
172
 
167
173
  # Initializes and connects a PostgreSQL adapter.
168
- def initialize(connection, logger, connection_parameters, config)
169
- super(connection, logger, config)
174
+ def initialize(...)
175
+ super
170
176
 
171
- @visitor = Arel::Visitors::PostgreSQL.new self
172
- @visitor.extend(ConnectionAdapters::DetermineIfPreparableVisitor) if defined?(ConnectionAdapters::DetermineIfPreparableVisitor)
173
- @prepared_statements = false
177
+ conn_params = @config.compact
174
178
 
175
- @raw_connection_parameters = connection_parameters
179
+ # Map ActiveRecords param names to PGs.
180
+ conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
181
+ conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
176
182
 
177
- # @local_tz is initialized as nil to avoid warnings when connect tries to use it
178
- @local_tz = nil
179
- @table_alias_length = nil
183
+ # Forward only valid config params to PG::Connection.connect.
184
+ conn_params.slice!(*RS_VALID_CONN_PARAMS)
180
185
 
181
- connect
182
- @statements = StatementPool.new @raw_connection,
183
- self.class.type_cast_config_to_integer(config[:statement_limit])
186
+ @connection_parameters = conn_params
184
187
 
185
- @type_map = Type::HashLookupTypeMap.new
186
- initialize_type_map(type_map)
187
- @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first['TimeZone']
188
- @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : false
189
- end
188
+ @max_identifier_length = nil
189
+ @type_map = nil
190
+ @raw_connection = nil
191
+ @notice_receiver_sql_warnings = []
192
+ @table_alias_length = nil
190
193
 
191
- # Clears the prepared statements cache.
192
- def clear_cache!(new_connection: false)
193
- @statements.clear
194
+ @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
194
195
  end
195
196
 
196
197
  def truncate(table_name, name = nil)
@@ -199,44 +200,55 @@ module ActiveRecord
199
200
 
200
201
  # Is this connection alive and ready for queries?
201
202
  def active?
202
- @raw_connection.query 'SELECT 1'
203
+ @lock.synchronize do
204
+ return false unless @raw_connection
205
+ @raw_connection.query ";"
206
+ end
203
207
  true
204
208
  rescue PG::Error
205
209
  false
206
210
  end
207
211
 
208
- def reload_type_map
209
- type_map.clear
210
- initialize_type_map
211
- end
212
+ def reload_type_map # :nodoc:
213
+ @lock.synchronize do
214
+ if @type_map
215
+ type_map.clear
216
+ else
217
+ @type_map = Type::HashLookupTypeMap.new
218
+ end
212
219
 
213
- # Close then reopen the connection.
214
- def reconnect!
215
- super
216
- @raw_connection.reset
217
- configure_connection
218
- reload_type_map
220
+ initialize_type_map
221
+ end
219
222
  end
220
223
 
221
224
  def reset!
222
- clear_cache!
223
- reset_transaction
224
- @raw_connection.query 'ROLLBACK' unless @raw_connection.transaction_status == ::PG::PQTRANS_IDLE
225
- @raw_connection.query 'DISCARD ALL'
226
- configure_connection
225
+ @lock.synchronize do
226
+ return connect! unless @raw_connection
227
+
228
+ unless @raw_connection.transaction_status == ::PG::PQTRANS_IDLE
229
+ @raw_connection.query "ROLLBACK"
230
+ end
231
+
232
+ super
233
+ end
227
234
  end
228
235
 
229
236
  # Disconnects from the database if already connected. Otherwise, this
230
237
  # method does nothing.
231
238
  def disconnect!
232
- super
233
- begin
234
- @raw_connection.close
235
- rescue StandardError
236
- nil
239
+ @lock.synchronize do
240
+ super
241
+ @raw_connection&.close rescue nil
242
+ @raw_connection = nil
237
243
  end
238
244
  end
239
245
 
246
+ def discard! # :nodoc:
247
+ super
248
+ @raw_connection&.socket_io&.reopen(IO::NULL) rescue nil
249
+ @raw_connection = nil
250
+ end
251
+
240
252
  def native_database_types # :nodoc:
241
253
  NATIVE_DATABASE_TYPES
242
254
  end
@@ -275,14 +287,22 @@ module ActiveRecord
275
287
  true
276
288
  end
277
289
 
278
- def enable_extension(name); end
290
+ def enable_extension(name)
291
+ ;
292
+ end
279
293
 
280
- def disable_extension(name); end
294
+ def disable_extension(name)
295
+ ;
296
+ end
281
297
 
282
298
  def extension_enabled?(_name)
283
299
  false
284
300
  end
285
301
 
302
+ def supports_common_table_expressions?
303
+ true
304
+ end
305
+
286
306
  # Returns the configured supported identifier length supported by PostgreSQL
287
307
  def table_alias_length
288
308
  @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
@@ -302,23 +322,26 @@ module ActiveRecord
302
322
  !native_database_types[type].nil?
303
323
  end
304
324
 
305
- def update_table_definition(table_name, base) # :nodoc:
325
+ def update_table_definition(table_name, base)
326
+ # :nodoc:
306
327
  Redshift::Table.new(table_name, base)
307
328
  end
308
329
 
309
- def lookup_cast_type(sql_type) # :nodoc:
330
+ def lookup_cast_type(sql_type)
331
+ # :nodoc:
310
332
  oid = execute("SELECT #{quote(sql_type)}::regtype::oid", 'SCHEMA').first['oid'].to_i
311
333
  super(oid)
312
334
  end
313
335
 
314
- def column_name_for_operation(operation, _node) # :nodoc:
336
+ def column_name_for_operation(operation, _node)
337
+ # :nodoc:
315
338
  OPERATION_ALIASES.fetch(operation) { operation.downcase }
316
339
  end
317
340
 
318
341
  OPERATION_ALIASES = { # :nodoc:
319
- 'maximum' => 'max',
320
- 'minimum' => 'min',
321
- 'average' => 'avg'
342
+ 'maximum' => 'max',
343
+ 'minimum' => 'min',
344
+ 'average' => 'avg'
322
345
  }.freeze
323
346
 
324
347
  protected
@@ -342,7 +365,8 @@ module ActiveRecord
342
365
  end
343
366
 
344
367
  class << self
345
- def initialize_type_map(m) # :nodoc:
368
+ def initialize_type_map(m)
369
+ # :nodoc:
346
370
  m.register_type 'int2', Type::Integer.new(limit: 2)
347
371
  m.register_type 'int4', Type::Integer.new(limit: 4)
348
372
  m.register_type 'int8', Type::Integer.new(limit: 8)
@@ -388,7 +412,8 @@ module ActiveRecord
388
412
 
389
413
  private
390
414
 
391
- def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
415
+ def get_oid_type(oid, fmod, column_name, sql_type = '')
416
+ # :nodoc:
392
417
  load_additional_types(type_map, [oid]) unless type_map.key?(oid)
393
418
 
394
419
  type_map.fetch(oid, fmod, sql_type) do
@@ -408,7 +433,8 @@ module ActiveRecord
408
433
  load_additional_types(m)
409
434
  end
410
435
 
411
- def extract_limit(sql_type) # :nodoc:
436
+ def extract_limit(sql_type)
437
+ # :nodoc:
412
438
  case sql_type
413
439
  when /^bigint/i, /^int8/i
414
440
  8
@@ -420,7 +446,8 @@ module ActiveRecord
420
446
  end
421
447
 
422
448
  # Extracts the value from a PostgreSQL column default definition.
423
- def extract_value_from_default(default) # :nodoc:
449
+ def extract_value_from_default(default)
450
+ # :nodoc:
424
451
  case default
425
452
  # Quoted types
426
453
  when /\A[(B]?'(.*)'::/m
@@ -441,19 +468,22 @@ module ActiveRecord
441
468
  end
442
469
  end
443
470
 
444
- def extract_default_function(default_value, default) # :nodoc:
471
+ def extract_default_function(default_value, default)
472
+ # :nodoc:
445
473
  default if has_default_function?(default_value, default)
446
474
  end
447
475
 
448
- def has_default_function?(default_value, default) # :nodoc:
476
+ def has_default_function?(default_value, default)
477
+ # :nodoc:
449
478
  !default_value && (/\w+\(.*\)/ === default)
450
479
  end
451
480
 
452
- def load_additional_types(type_map, oids = nil) # :nodoc:
481
+ def load_additional_types(type_map, oids = nil)
482
+ # :nodoc:
453
483
  initializer = OID::TypeMapInitializer.new(type_map)
454
484
 
455
485
  load_types_queries(initializer, oids) do |query|
456
- execute_and_clear(query, 'SCHEMA', []) do |records|
486
+ execute_and_clear(query, 'SCHEMA', [], allow_retry: true, materialize_transactions: false) do |records|
457
487
  initializer.run(records)
458
488
  end
459
489
  end
@@ -483,58 +513,69 @@ module ActiveRecord
483
513
 
484
514
  FEATURE_NOT_SUPPORTED = '0A000' # :nodoc:
485
515
 
486
- def execute_and_clear(sql, name, binds, prepare: false, async: false)
487
- result =
488
- if without_prepared_statement?(binds)
489
- exec_no_cache(sql, name, [])
490
- elsif !prepare
491
- exec_no_cache(sql, name, binds)
492
- else
493
- exec_cache(sql, name, binds)
494
- end
516
+ def execute_and_clear(sql, name, binds, prepare: false, async: false, allow_retry: false, materialize_transactions: true)
517
+ sql = transform_query(sql)
518
+ check_if_write_query(sql)
495
519
 
496
- ret = yield result
497
- result.clear
520
+ if !prepare || without_prepared_statement?(binds)
521
+ result = exec_no_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
522
+ else
523
+ result = exec_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
524
+ end
525
+ begin
526
+ ret = yield result
527
+ ensure
528
+ result.clear
529
+ end
498
530
  ret
499
531
  end
500
532
 
501
- def exec_no_cache(sql, name, binds)
502
- materialize_transactions
533
+ def exec_no_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
534
+ mark_transaction_written_if_write(sql)
503
535
 
504
536
  # make sure we carry over any changes to ActiveRecord.default_timezone that have been
505
537
  # made since we established the connection
506
538
  update_typemap_for_default_timezone
507
539
 
508
540
  type_casted_binds = type_casted_binds(binds)
509
- log(sql, name, binds, type_casted_binds) do
510
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
511
- @raw_connection.exec_params(sql, type_casted_binds)
541
+ log(sql, name, binds, type_casted_binds, async: async) do
542
+ with_raw_connection do |conn|
543
+ result = conn.exec_params(sql, type_casted_binds)
544
+ verified!
545
+ result
512
546
  end
513
547
  end
514
548
  end
515
549
 
516
- def exec_cache(sql, name, binds)
517
- materialize_transactions
550
+ def exec_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
551
+ mark_transaction_written_if_write(sql)
552
+
518
553
  update_typemap_for_default_timezone
519
554
 
520
- stmt_key = prepare_statement(sql, binds)
521
- type_casted_binds = type_casted_binds(binds)
555
+ with_raw_connection do |conn|
556
+ stmt_key = prepare_statement(sql, binds, conn)
557
+ type_casted_binds = type_casted_binds(binds)
522
558
 
523
- log(sql, name, binds, type_casted_binds, stmt_key) do
524
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
525
- @raw_connection.exec_prepared(stmt_key, type_casted_binds)
559
+ log(sql, name, binds, type_casted_binds, stmt_key, async: async) do
560
+ result = conn.exec_prepared(stmt_key, type_casted_binds)
561
+ verified!
562
+ result
526
563
  end
527
564
  end
528
565
  rescue ActiveRecord::StatementInvalid => e
529
566
  raise unless is_cached_plan_failure?(e)
530
- raise ActiveRecord::PreparedStatementCacheExpired, e.cause.message if in_transaction?
531
567
 
532
- @lock.synchronize do
533
- # outside of transactions we can simply flush this query and retry
534
- @statements.delete sql_key(sql)
568
+ # Nothing we can do if we are in a transaction because all commands
569
+ # will raise InFailedSQLTransaction
570
+ if in_transaction?
571
+ raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
572
+ else
573
+ @lock.synchronize do
574
+ # outside of transactions we can simply flush this query and retry
575
+ @statements.delete sql_key(sql)
576
+ end
577
+ retry
535
578
  end
536
-
537
- retry
538
579
  end
539
580
 
540
581
  # Annoyingly, the code for prepared statements whose return value may
@@ -547,6 +588,7 @@ module ActiveRecord
547
588
  # Check here for more details:
548
589
  # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
549
590
  CACHED_PLAN_HEURISTIC = 'cached plan must not change result type'
591
+
550
592
  def is_cached_plan_failure?(e)
551
593
  pgerror = e.cause
552
594
  code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
@@ -563,51 +605,60 @@ module ActiveRecord
563
605
 
564
606
  # Prepare the statement if it hasn't been prepared, return
565
607
  # the statement key.
566
- def prepare_statement(sql, binds)
567
- @lock.synchronize do
568
- sql_key = sql_key(sql)
569
- unless @statements.key? sql_key
570
- nextkey = @statements.next_key
571
- begin
572
- @raw_connection.prepare nextkey, sql
573
- rescue StandardError => e
574
- raise translate_exception_class(e, sql, binds)
575
- end
576
- # Clear the queue
577
- @raw_connection.get_last_result
578
- @statements[sql_key] = nextkey
608
+ def prepare_statement(sql, binds, conn)
609
+ sql_key = sql_key(sql)
610
+ unless @statements.key? sql_key
611
+ nextkey = @statements.next_key
612
+ begin
613
+ conn.prepare nextkey, sql
614
+ rescue => e
615
+ raise translate_exception_class(e, sql, binds)
579
616
  end
580
- @statements[sql_key]
617
+ # Clear the queue
618
+ conn.get_last_result
619
+ @statements[sql_key] = nextkey
581
620
  end
621
+ @statements[sql_key]
582
622
  end
583
623
 
584
624
  # Connects to a PostgreSQL server and sets up the adapter depending on the
585
625
  # connected server's characteristics.
586
626
  def connect
587
- @raw_connection = PG.connect(@raw_connection_parameters)
588
- configure_connection
589
- add_pg_encoders
590
- add_pg_decoders
627
+ @raw_connection = self.class.new_client(@connection_parameters)
628
+ rescue ConnectionNotEstablished => ex
629
+ raise ex.set_pool(@pool)
630
+ end
631
+
632
+ def reconnect
633
+ begin
634
+ @raw_connection&.reset
635
+ rescue PG::ConnectionBad
636
+ @raw_connection = nil
637
+ end
638
+
639
+ connect unless @raw_connection
640
+ end
641
+
642
+ def reconnect
643
+ begin
644
+ @raw_connection&.reset
645
+ rescue PG::ConnectionBad
646
+ @raw_connection = nil
647
+ end
648
+
649
+ connect unless @raw_connection
591
650
  end
592
651
 
593
652
  # Configures the encoding, verbosity, schema search path, and time zone of the connection.
594
653
  # This is called by #connect and should not be called manually.
595
654
  def configure_connection
596
- @raw_connection.set_client_encoding(@config[:encoding]) if @config[:encoding]
655
+ if @config[:encoding]
656
+ @raw_connection.set_client_encoding(@config[:encoding])
657
+ end
597
658
  self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
598
659
 
599
660
  variables = @config.fetch(:variables, {}).stringify_keys
600
661
 
601
- # If using Active Record's time zone support configure the connection to return
602
- # TIMESTAMP WITH ZONE types in UTC.
603
- unless variables['timezone']
604
- if ActiveRecord.default_timezone == :utc
605
- variables['timezone'] = 'UTC'
606
- elsif @local_tz
607
- variables['timezone'] = @local_tz
608
- end
609
- end
610
-
611
662
  # SET statements from :variables config hash
612
663
  # https://www.postgresql.org/docs/current/static/sql-set.html
613
664
  variables.map do |k, v|
@@ -618,9 +669,32 @@ module ActiveRecord
618
669
  execute("SET #{k} TO #{quote(v)}", 'SCHEMA')
619
670
  end
620
671
  end
672
+
673
+ add_pg_encoders
674
+ add_pg_decoders
675
+
676
+ reload_type_map
677
+ end
678
+
679
+ def reconfigure_connection_timezone
680
+ variables = @config.fetch(:variables, {}).stringify_keys
681
+
682
+ # If it's been directly configured as a connection variable, we don't
683
+ # need to do anything here; it will be set up by configure_connection
684
+ # and then never changed.
685
+ return if variables["timezone"]
686
+
687
+ # If using Active Record's time zone support configure the connection
688
+ # to return TIMESTAMP WITH ZONE types in UTC.
689
+ if default_timezone == :utc
690
+ internal_execute("SET timezone TO 'UTC'")
691
+ else
692
+ internal_execute("SET timezone TO DEFAULT")
693
+ end
621
694
  end
622
695
 
623
- def last_insert_id_result(sequence_name) # :nodoc:
696
+ def last_insert_id_result(sequence_name)
697
+ # :nodoc:
624
698
  exec_query("SELECT currval('#{sequence_name}')", 'SQL')
625
699
  end
626
700
 
@@ -642,7 +716,8 @@ module ActiveRecord
642
716
  # Query implementation notes:
643
717
  # - format_type includes the column size constraint, e.g. varchar(50)
644
718
  # - ::regclass is a function that gives the id for a table name
645
- def column_definitions(table_name) # :nodoc:
719
+ def column_definitions(table_name)
720
+ # :nodoc:
646
721
  query(<<-END_SQL, 'SCHEMA')
647
722
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
648
723
  pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
@@ -670,23 +745,23 @@ module ActiveRecord
670
745
  def can_perform_case_insensitive_comparison_for?(column)
671
746
  @case_insensitive_cache ||= {}
672
747
  @case_insensitive_cache[column.sql_type] ||= begin
673
- sql = <<~SQL
674
- SELECT exists(
675
- SELECT * FROM pg_proc
676
- WHERE proname = 'lower'
677
- AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
678
- ) OR exists(
679
- SELECT * FROM pg_proc
680
- INNER JOIN pg_cast
681
- ON ARRAY[casttarget]::oidvector = proargtypes
682
- WHERE proname = 'lower'
683
- AND castsource = #{quote column.sql_type}::regtype
684
- )
685
- SQL
686
- execute_and_clear(sql, 'SCHEMA', []) do |result|
687
- result.getvalue(0, 0)
688
- end
689
- end
748
+ sql = <<~SQL
749
+ SELECT exists(
750
+ SELECT * FROM pg_proc
751
+ WHERE proname = 'lower'
752
+ AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
753
+ ) OR exists(
754
+ SELECT * FROM pg_proc
755
+ INNER JOIN pg_cast
756
+ ON ARRAY[casttarget]::oidvector = proargtypes
757
+ WHERE proname = 'lower'
758
+ AND castsource = #{quote column.sql_type}::regtype
759
+ )
760
+ SQL
761
+ execute_and_clear(sql, 'SCHEMA', []) do |result|
762
+ result.getvalue(0, 0)
763
+ end
764
+ end
690
765
  end
691
766
 
692
767
  def add_pg_encoders
@@ -698,22 +773,22 @@ module ActiveRecord
698
773
  end
699
774
 
700
775
  def update_typemap_for_default_timezone
701
- return if @default_timezone == ActiveRecord.default_timezone || !@timestamp_decoder
776
+ if @raw_connection && @mapped_default_timezone != default_timezone && @timestamp_decoder
777
+ decoder_class = default_timezone == :utc ?
778
+ PG::TextDecoder::TimestampUtc :
779
+ PG::TextDecoder::TimestampWithoutTimeZone
702
780
 
703
- decoder_class =
704
- if ActiveRecord.default_timezone == :utc
705
- PG::TextDecoder::TimestampUtc
706
- else
707
- PG::TextDecoder::TimestampWithoutTimeZone
708
- end
781
+ @timestamp_decoder = decoder_class.new(**@timestamp_decoder.to_h)
782
+ @raw_connection.type_map_for_results.add_coder(@timestamp_decoder)
709
783
 
710
- @timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
711
- @raw_connection.type_map_for_results.add_coder(@timestamp_decoder)
712
- @default_timezone = ActiveRecord.default_timezone
784
+ @mapped_default_timezone = default_timezone
713
785
 
714
- # if default timezone has changed, we need to reconfigure the connection
715
- # (specifically, the session time zone)
716
- configure_connection
786
+ # if default timezone has changed, we need to reconfigure the connection
787
+ # (specifically, the session time zone)
788
+ reconfigure_connection_timezone
789
+
790
+ true
791
+ end
717
792
  end
718
793
 
719
794
  def add_pg_decoders
@@ -742,7 +817,7 @@ module ActiveRecord
742
817
  FROM pg_type as t
743
818
  WHERE t.typname IN (%s)
744
819
  SQL
745
- coders = execute_and_clear(query, 'SCHEMA', []) do |result|
820
+ coders = execute_and_clear(query, 'SCHEMA', [], allow_retry: true, materialize_transactions: false) do |result|
746
821
  result.filter_map { |row| construct_coder(row, coders_by_name[row['typname']]) }
747
822
  end
748
823
 
@@ -750,6 +825,9 @@ module ActiveRecord
750
825
  coders.each { |coder| map.add_coder(coder) }
751
826
  @raw_connection.type_map_for_results = map
752
827
 
828
+ @type_map_for_results = PG::TypeMapByOid.new
829
+ @type_map_for_results.default_type_map = map
830
+
753
831
  # extract timestamp decoder for use in update_typemap_for_default_timezone
754
832
  @timestamp_decoder = coders.find { |coder| coder.name == 'timestamp' }
755
833
  update_typemap_for_default_timezone
@@ -761,7 +839,8 @@ module ActiveRecord
761
839
  coder_class.new(oid: row['oid'].to_i, name: row['typname'])
762
840
  end
763
841
 
764
- def create_table_definition(*args, **options) # :nodoc:
842
+ def create_table_definition(*args, **options)
843
+ # :nodoc:
765
844
  Redshift::TableDefinition.new(self, *args, **options)
766
845
  end
767
846
  end