activerecord7-redshift-adapter-pennylane 1.0.2 → 1.0.3

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.
@@ -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