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.
- checksums.yaml +4 -4
- data/lib/active_record/connection_adapters/redshift_7_0/schema_statements.rb +14 -16
- data/lib/active_record/connection_adapters/redshift_7_1/database_statements.rb +122 -174
- data/lib/active_record/connection_adapters/redshift_7_1/quoting.rb +78 -16
- data/lib/active_record/connection_adapters/redshift_7_1/schema_statements.rb +14 -16
- data/lib/active_record/connection_adapters/redshift_7_1/type_metadata.rb +18 -14
- data/lib/active_record/connection_adapters/redshift_7_1_adapter.rb +257 -178
- metadata +2 -2
@@ -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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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(
|
169
|
-
super
|
174
|
+
def initialize(...)
|
175
|
+
super
|
170
176
|
|
171
|
-
|
172
|
-
@visitor.extend(ConnectionAdapters::DetermineIfPreparableVisitor) if defined?(ConnectionAdapters::DetermineIfPreparableVisitor)
|
173
|
-
@prepared_statements = false
|
177
|
+
conn_params = @config.compact
|
174
178
|
|
175
|
-
|
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
|
-
#
|
178
|
-
|
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
|
-
|
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
|
-
@
|
186
|
-
|
187
|
-
@
|
188
|
-
@
|
189
|
-
|
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
|
-
|
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
|
-
@
|
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
|
-
|
210
|
-
|
211
|
-
|
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
|
-
|
214
|
-
|
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
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
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
|
-
|
233
|
-
|
234
|
-
@raw_connection
|
235
|
-
|
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)
|
290
|
+
def enable_extension(name)
|
291
|
+
;
|
292
|
+
end
|
279
293
|
|
280
|
-
def disable_extension(name)
|
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)
|
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)
|
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)
|
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
|
-
|
320
|
-
|
321
|
-
|
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)
|
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 = '')
|
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)
|
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)
|
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)
|
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)
|
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)
|
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
|
-
|
488
|
-
|
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
|
-
|
497
|
-
|
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
|
-
|
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
|
-
|
511
|
-
|
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
|
-
|
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
|
-
|
521
|
-
|
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
|
-
|
524
|
-
|
525
|
-
|
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
|
-
|
533
|
-
|
534
|
-
|
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
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
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
|
-
|
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 =
|
588
|
-
|
589
|
-
|
590
|
-
|
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
|
-
|
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)
|
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)
|
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
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
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
|
-
|
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
|
-
|
704
|
-
|
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
|
-
|
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
|
-
|
715
|
-
|
716
|
-
|
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)
|
842
|
+
def create_table_definition(*args, **options)
|
843
|
+
# :nodoc:
|
765
844
|
Redshift::TableDefinition.new(self, *args, **options)
|
766
845
|
end
|
767
846
|
end
|