activerecord6-redshift-adapter 1.1.2 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/active_record/connection_adapters/redshift/column.rb +12 -2
- data/lib/active_record/connection_adapters/redshift/database_statements.rb +16 -3
- data/lib/active_record/connection_adapters/redshift/oid/decimal.rb +1 -1
- data/lib/active_record/connection_adapters/redshift/schema_definitions.rb +5 -5
- data/lib/active_record/connection_adapters/redshift/schema_statements.rb +41 -20
- data/lib/active_record/connection_adapters/redshift_adapter.rb +197 -91
- metadata +10 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 95fe63dabddcdce38d3cf4e84d8a891a682ddcbd5884ec19bd0c1907693d1773
|
4
|
+
data.tar.gz: 00c59c4c3f97f14c08a9f140399e9a698d19b7ad5cd18d65e6291c23f7fb9486
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 07a977cc1444f6e4e303b1b1aa884c736d0eed3b276e04cb706f9b89cad0e24dde2f9a4519fb1647238b80199ae51662d67a499299e0f9a4a44dff36b4598109
|
7
|
+
data.tar.gz: 1fb3715ef714636c9ae987e759b4a2501c87af1e721f32aedf400b2c7436a0582dba50c5a671ea6595d2bb9ad1633f2c8d444cf8be7ec2537d1a6a75723b3457
|
@@ -3,8 +3,18 @@ module ActiveRecord
|
|
3
3
|
class RedshiftColumn < Column #:nodoc:
|
4
4
|
delegate :oid, :fmod, to: :sql_type_metadata
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
if ActiveRecord::VERSION::MAJOR >= 6 && ActiveRecord::VERSION::MINOR >= 1
|
7
|
+
# Required for Rails 6.1, see https://github.com/rails/rails/pull/41756
|
8
|
+
mattr_reader :array, default: false
|
9
|
+
alias :array? :array
|
10
|
+
|
11
|
+
def initialize(name, default, sql_type_metadata, null = true, default_function = nil, **)
|
12
|
+
super name, default, sql_type_metadata, null, default_function
|
13
|
+
end
|
14
|
+
else
|
15
|
+
def initialize(name, default, sql_type_metadata, null = true, default_function = nil)
|
16
|
+
super name, default, sql_type_metadata, null, default_function
|
17
|
+
end
|
8
18
|
end
|
9
19
|
end
|
10
20
|
end
|
@@ -47,9 +47,12 @@ module ActiveRecord
|
|
47
47
|
def select_value(arel, name = nil, binds = [])
|
48
48
|
# In Rails 5.2, arel_from_relation replaced binds_from_relation,
|
49
49
|
# so we see which method exists to get the variables
|
50
|
+
#
|
51
|
+
# In Rails 6.0 to_sql_and_binds began only returning sql, with
|
52
|
+
# to_sql_and_binds serving as a replacement
|
50
53
|
if respond_to?(:arel_from_relation, true)
|
51
54
|
arel = arel_from_relation(arel)
|
52
|
-
sql, binds =
|
55
|
+
sql, binds = to_sql_and_binds(arel, binds)
|
53
56
|
else
|
54
57
|
arel, binds = binds_from_relation arel, binds
|
55
58
|
sql = to_sql(arel, binds)
|
@@ -62,9 +65,12 @@ module ActiveRecord
|
|
62
65
|
def select_values(arel, name = nil)
|
63
66
|
# In Rails 5.2, arel_from_relation replaced binds_from_relation,
|
64
67
|
# so we see which method exists to get the variables
|
68
|
+
#
|
69
|
+
# In Rails 6.0 to_sql_and_binds began only returning sql, with
|
70
|
+
# to_sql_and_binds serving as a replacement
|
65
71
|
if respond_to?(:arel_from_relation, true)
|
66
72
|
arel = arel_from_relation(arel)
|
67
|
-
sql, binds =
|
73
|
+
sql, binds = to_sql_and_binds(arel, [])
|
68
74
|
else
|
69
75
|
arel, binds = binds_from_relation arel, []
|
70
76
|
sql = to_sql(arel, binds)
|
@@ -81,7 +87,14 @@ module ActiveRecord
|
|
81
87
|
|
82
88
|
# Executes a SELECT query and returns an array of rows. Each row is an
|
83
89
|
# array of field values.
|
84
|
-
def select_rows(
|
90
|
+
def select_rows(arel, name = nil, binds = [])
|
91
|
+
if respond_to?(:arel_from_relation, true)
|
92
|
+
arel = arel_from_relation(arel)
|
93
|
+
sql, binds = to_sql_and_binds(arel, [])
|
94
|
+
else
|
95
|
+
arel, binds = binds_from_relation arel, []
|
96
|
+
sql = to_sql(arel, binds)
|
97
|
+
end
|
85
98
|
execute_and_clear(sql, name, binds) do |result|
|
86
99
|
result.values
|
87
100
|
end
|
@@ -30,18 +30,18 @@ module ActiveRecord
|
|
30
30
|
# require you to assure that you always provide a UUID value before saving
|
31
31
|
# a record (as primary keys cannot be +nil+). This might be done via the
|
32
32
|
# +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
|
33
|
-
def primary_key(name, type = :primary_key, options
|
33
|
+
def primary_key(name, type = :primary_key, **options)
|
34
34
|
return super unless type == :uuid
|
35
35
|
options[:default] = options.fetch(:default, 'uuid_generate_v4()')
|
36
36
|
options[:primary_key] = true
|
37
37
|
column name, type, options
|
38
38
|
end
|
39
39
|
|
40
|
-
def json(name, options
|
40
|
+
def json(name, **options)
|
41
41
|
column(name, :json, options)
|
42
42
|
end
|
43
43
|
|
44
|
-
def jsonb(name, options
|
44
|
+
def jsonb(name, **options)
|
45
45
|
column(name, :jsonb, options)
|
46
46
|
end
|
47
47
|
end
|
@@ -54,8 +54,8 @@ module ActiveRecord
|
|
54
54
|
|
55
55
|
private
|
56
56
|
|
57
|
-
def create_column_definition(
|
58
|
-
Redshift::ColumnDefinition.new
|
57
|
+
def create_column_definition(*args)
|
58
|
+
Redshift::ColumnDefinition.new(*args)
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -1,28 +1,49 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module ConnectionAdapters
|
3
3
|
module Redshift
|
4
|
-
class SchemaCreation < AbstractAdapter::SchemaCreation
|
5
|
-
private
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
if ActiveRecord::VERSION::MAJOR >= 6 && ActiveRecord::VERSION::MINOR >= 1
|
6
|
+
class SchemaCreation < SchemaCreation
|
7
|
+
private
|
8
|
+
|
9
|
+
def visit_ColumnDefinition(o)
|
10
|
+
o.sql_type = type_to_sql(o.type, limit: o.limit, precision: o.precision, scale: o.scale)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_column_options!(sql, options)
|
15
|
+
column = options.fetch(:column) { return super }
|
16
|
+
if column.type == :uuid && options[:default] =~ /\(\)/
|
17
|
+
sql << " DEFAULT #{options[:default]}"
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
10
22
|
end
|
23
|
+
else
|
24
|
+
class SchemaCreation < AbstractAdapter::SchemaCreation
|
25
|
+
private
|
11
26
|
|
12
|
-
|
13
|
-
|
14
|
-
if column.type == :uuid && options[:default] =~ /\(\)/
|
15
|
-
sql << " DEFAULT #{options[:default]}"
|
16
|
-
else
|
27
|
+
def visit_ColumnDefinition(o)
|
28
|
+
o.sql_type = type_to_sql(o.type, limit: o.limit, precision: o.precision, scale: o.scale)
|
17
29
|
super
|
18
30
|
end
|
31
|
+
|
32
|
+
def add_column_options!(sql, options)
|
33
|
+
column = options.fetch(:column) { return super }
|
34
|
+
if column.type == :uuid && options[:default] =~ /\(\)/
|
35
|
+
sql << " DEFAULT #{options[:default]}"
|
36
|
+
else
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
19
40
|
end
|
20
41
|
end
|
21
42
|
|
22
43
|
module SchemaStatements
|
23
44
|
# Drops the database specified on the +name+ attribute
|
24
45
|
# and creates it again using the provided +options+.
|
25
|
-
def recreate_database(name, options
|
46
|
+
def recreate_database(name, **options) #:nodoc:
|
26
47
|
drop_database(name)
|
27
48
|
create_database(name, options)
|
28
49
|
end
|
@@ -35,7 +56,7 @@ module ActiveRecord
|
|
35
56
|
# Example:
|
36
57
|
# create_database config[:database], config
|
37
58
|
# create_database 'foo_development', encoding: 'unicode'
|
38
|
-
def create_database(name, options
|
59
|
+
def create_database(name, **options)
|
39
60
|
options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
|
40
61
|
|
41
62
|
option_string = options.inject("") do |memo, (key, value)|
|
@@ -130,7 +151,7 @@ module ActiveRecord
|
|
130
151
|
SQL
|
131
152
|
end
|
132
153
|
|
133
|
-
def drop_table(table_name, options
|
154
|
+
def drop_table(table_name, **options)
|
134
155
|
execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
135
156
|
end
|
136
157
|
|
@@ -200,7 +221,7 @@ module ActiveRecord
|
|
200
221
|
end
|
201
222
|
|
202
223
|
# Drops the schema for the given schema name.
|
203
|
-
def drop_schema(schema_name, options
|
224
|
+
def drop_schema(schema_name, **options)
|
204
225
|
execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
|
205
226
|
end
|
206
227
|
|
@@ -268,20 +289,20 @@ module ActiveRecord
|
|
268
289
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
269
290
|
end
|
270
291
|
|
271
|
-
def add_column(table_name, column_name, type, options
|
292
|
+
def add_column(table_name, column_name, type, **options) #:nodoc:
|
272
293
|
clear_cache!
|
273
294
|
super
|
274
295
|
end
|
275
296
|
|
276
297
|
# Changes the column of a table.
|
277
|
-
def change_column(table_name, column_name, type, options
|
298
|
+
def change_column(table_name, column_name, type, **options)
|
278
299
|
clear_cache!
|
279
300
|
quoted_table_name = quote_table_name(table_name)
|
280
|
-
sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
|
301
|
+
sql_type = type_to_sql(type, limit: options[:limit], precision: options[:precision], scale: options[:scale])
|
281
302
|
sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
|
282
303
|
sql << " USING #{options[:using]}" if options[:using]
|
283
304
|
if options[:cast_as]
|
284
|
-
sql << " USING CAST(#{quote_column_name(column_name)} AS #{type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale])})"
|
305
|
+
sql << " USING CAST(#{quote_column_name(column_name)} AS #{type_to_sql(options[:cast_as], limit: options[:limit], precision: options[:precision], scale: options[:scale])})"
|
285
306
|
end
|
286
307
|
execute sql
|
287
308
|
|
@@ -321,7 +342,7 @@ module ActiveRecord
|
|
321
342
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
322
343
|
end
|
323
344
|
|
324
|
-
def add_index(table_name, column_name, options
|
345
|
+
def add_index(table_name, column_name, **options) #:nodoc:
|
325
346
|
end
|
326
347
|
|
327
348
|
def remove_index!(table_name, index_name) #:nodoc:
|
@@ -372,7 +393,7 @@ module ActiveRecord
|
|
372
393
|
end
|
373
394
|
|
374
395
|
# Maps logical Rails types to PostgreSQL-specific data types.
|
375
|
-
def type_to_sql(type, limit
|
396
|
+
def type_to_sql(type, limit: nil, precision: nil, scale: nil, **)
|
376
397
|
case type.to_s
|
377
398
|
when 'integer'
|
378
399
|
return 'integer' unless limit
|
@@ -12,10 +12,14 @@ require 'active_record/connection_adapters/redshift/schema_statements'
|
|
12
12
|
require 'active_record/connection_adapters/redshift/type_metadata'
|
13
13
|
require 'active_record/connection_adapters/redshift/database_statements'
|
14
14
|
|
15
|
+
require 'active_record/tasks/database_tasks'
|
16
|
+
|
15
17
|
require 'pg'
|
16
18
|
|
17
19
|
require 'ipaddr'
|
18
20
|
|
21
|
+
ActiveRecord::Tasks::DatabaseTasks.register_task(/redshift/, "ActiveRecord::Tasks::PostgreSQLDatabaseTasks")
|
22
|
+
|
19
23
|
module ActiveRecord
|
20
24
|
module ConnectionHandling # :nodoc:
|
21
25
|
RS_VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
|
@@ -78,10 +82,10 @@ module ActiveRecord
|
|
78
82
|
string: { name: "varchar" },
|
79
83
|
text: { name: "varchar" },
|
80
84
|
integer: { name: "integer" },
|
81
|
-
float: { name: "
|
85
|
+
float: { name: "decimal" },
|
82
86
|
decimal: { name: "decimal" },
|
83
87
|
datetime: { name: "timestamp" },
|
84
|
-
time: { name: "
|
88
|
+
time: { name: "timestamp" },
|
85
89
|
date: { name: "date" },
|
86
90
|
bigint: { name: "bigint" },
|
87
91
|
boolean: { name: "boolean" },
|
@@ -122,55 +126,29 @@ module ActiveRecord
|
|
122
126
|
{ concurrently: 'CONCURRENTLY' }
|
123
127
|
end
|
124
128
|
|
125
|
-
class StatementPool < ConnectionAdapters::StatementPool
|
129
|
+
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
126
130
|
def initialize(connection, max)
|
127
131
|
super(max)
|
128
132
|
@connection = connection
|
129
133
|
@counter = 0
|
130
|
-
@cache = Hash.new { |h,pid| h[pid] = {} }
|
131
134
|
end
|
132
135
|
|
133
|
-
def each(&block); cache.each(&block); end
|
134
|
-
def key?(key); cache.key?(key); end
|
135
|
-
def [](key); cache[key]; end
|
136
|
-
def length; cache.length; end
|
137
|
-
|
138
136
|
def next_key
|
139
137
|
"a#{@counter + 1}"
|
140
138
|
end
|
141
139
|
|
142
140
|
def []=(sql, key)
|
143
|
-
|
144
|
-
dealloc(cache.shift.last)
|
145
|
-
end
|
146
|
-
@counter += 1
|
147
|
-
cache[sql] = key
|
148
|
-
end
|
149
|
-
|
150
|
-
def clear
|
151
|
-
cache.each_value do |stmt_key|
|
152
|
-
dealloc stmt_key
|
153
|
-
end
|
154
|
-
cache.clear
|
155
|
-
end
|
156
|
-
|
157
|
-
def delete(sql_key)
|
158
|
-
dealloc cache[sql_key]
|
159
|
-
cache.delete sql_key
|
141
|
+
super.tap { @counter += 1 }
|
160
142
|
end
|
161
143
|
|
162
144
|
private
|
163
|
-
|
164
|
-
def cache
|
165
|
-
@cache[Process.pid]
|
166
|
-
end
|
167
|
-
|
168
145
|
def dealloc(key)
|
169
146
|
@connection.query "DEALLOCATE #{key}" if connection_active?
|
147
|
+
rescue PG::Error
|
170
148
|
end
|
171
149
|
|
172
150
|
def connection_active?
|
173
|
-
@connection.status == PG::
|
151
|
+
@connection.status == PG::CONNECTION_OK
|
174
152
|
rescue PG::Error
|
175
153
|
false
|
176
154
|
end
|
@@ -181,7 +159,7 @@ module ActiveRecord
|
|
181
159
|
super(connection, logger, config)
|
182
160
|
|
183
161
|
@visitor = Arel::Visitors::PostgreSQL.new self
|
184
|
-
@visitor.extend(ConnectionAdapters::DetermineIfPreparableVisitor)
|
162
|
+
@visitor.extend(ConnectionAdapters::DetermineIfPreparableVisitor) if defined?(ConnectionAdapters::DetermineIfPreparableVisitor)
|
185
163
|
@prepared_statements = false
|
186
164
|
|
187
165
|
@connection_parameters = connection_parameters
|
@@ -255,14 +233,6 @@ module ActiveRecord
|
|
255
233
|
true
|
256
234
|
end
|
257
235
|
|
258
|
-
# Enable standard-conforming strings if available.
|
259
|
-
def set_standard_conforming_strings
|
260
|
-
old, self.client_min_messages = client_min_messages, 'panic'
|
261
|
-
execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
|
262
|
-
ensure
|
263
|
-
self.client_min_messages = old
|
264
|
-
end
|
265
|
-
|
266
236
|
def supports_ddl_transactions?
|
267
237
|
true
|
268
238
|
end
|
@@ -342,7 +312,7 @@ module ActiveRecord
|
|
342
312
|
@connection.server_version
|
343
313
|
end
|
344
314
|
|
345
|
-
def translate_exception(exception, message)
|
315
|
+
def translate_exception(exception, message:, sql:, binds:)
|
346
316
|
return exception unless exception.respond_to?(:result)
|
347
317
|
|
348
318
|
case exception.message
|
@@ -496,39 +466,68 @@ module ActiveRecord
|
|
496
466
|
ret
|
497
467
|
end
|
498
468
|
|
469
|
+
|
499
470
|
def exec_no_cache(sql, name, binds)
|
500
|
-
|
471
|
+
materialize_transactions
|
472
|
+
|
473
|
+
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
|
474
|
+
# made since we established the connection
|
475
|
+
update_typemap_for_default_timezone
|
476
|
+
|
477
|
+
type_casted_binds = type_casted_binds(binds)
|
478
|
+
log(sql, name, binds, type_casted_binds) do
|
479
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
480
|
+
@connection.exec_params(sql, type_casted_binds)
|
481
|
+
end
|
482
|
+
end
|
501
483
|
end
|
502
484
|
|
503
485
|
def exec_cache(sql, name, binds)
|
504
|
-
|
505
|
-
|
506
|
-
[col, type_cast(val, col)]
|
507
|
-
}
|
486
|
+
materialize_transactions
|
487
|
+
update_typemap_for_default_timezone
|
508
488
|
|
509
|
-
|
510
|
-
|
489
|
+
stmt_key = prepare_statement(sql, binds)
|
490
|
+
type_casted_binds = type_casted_binds(binds)
|
491
|
+
|
492
|
+
log(sql, name, binds, type_casted_binds, stmt_key) do
|
493
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
494
|
+
@connection.exec_prepared(stmt_key, type_casted_binds)
|
495
|
+
end
|
511
496
|
end
|
512
497
|
rescue ActiveRecord::StatementInvalid => e
|
513
|
-
|
514
|
-
|
515
|
-
#
|
516
|
-
#
|
517
|
-
|
518
|
-
|
519
|
-
begin
|
520
|
-
code = pgerror.result.result_error_field(PG::Result::PG_DIAG_SQLSTATE)
|
521
|
-
rescue
|
522
|
-
raise e
|
523
|
-
end
|
524
|
-
if FEATURE_NOT_SUPPORTED == code
|
525
|
-
@statements.delete sql_key(sql)
|
526
|
-
retry
|
498
|
+
raise unless is_cached_plan_failure?(e)
|
499
|
+
|
500
|
+
# Nothing we can do if we are in a transaction because all commands
|
501
|
+
# will raise InFailedSQLTransaction
|
502
|
+
if in_transaction?
|
503
|
+
raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
|
527
504
|
else
|
528
|
-
|
505
|
+
@lock.synchronize do
|
506
|
+
# outside of transactions we can simply flush this query and retry
|
507
|
+
@statements.delete sql_key(sql)
|
508
|
+
end
|
509
|
+
retry
|
529
510
|
end
|
530
511
|
end
|
531
512
|
|
513
|
+
# Annoyingly, the code for prepared statements whose return value may
|
514
|
+
# have changed is FEATURE_NOT_SUPPORTED.
|
515
|
+
#
|
516
|
+
# This covers various different error types so we need to do additional
|
517
|
+
# work to classify the exception definitively as a
|
518
|
+
# ActiveRecord::PreparedStatementCacheExpired
|
519
|
+
#
|
520
|
+
# Check here for more details:
|
521
|
+
# https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
522
|
+
CACHED_PLAN_HEURISTIC = "cached plan must not change result type"
|
523
|
+
def is_cached_plan_failure?(e)
|
524
|
+
pgerror = e.cause
|
525
|
+
code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
|
526
|
+
code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
|
527
|
+
rescue
|
528
|
+
false
|
529
|
+
end
|
530
|
+
|
532
531
|
# Returns the statement identifier for the client side cache
|
533
532
|
# of statements
|
534
533
|
def sql_key(sql)
|
@@ -537,34 +536,31 @@ module ActiveRecord
|
|
537
536
|
|
538
537
|
# Prepare the statement if it hasn't been prepared, return
|
539
538
|
# the statement key.
|
540
|
-
def prepare_statement(sql)
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
539
|
+
def prepare_statement(sql, binds)
|
540
|
+
@lock.synchronize do
|
541
|
+
sql_key = sql_key(sql)
|
542
|
+
unless @statements.key? sql_key
|
543
|
+
nextkey = @statements.next_key
|
544
|
+
begin
|
545
|
+
@connection.prepare nextkey, sql
|
546
|
+
rescue => e
|
547
|
+
raise translate_exception_class(e, sql, binds)
|
548
|
+
end
|
549
|
+
# Clear the queue
|
550
|
+
@connection.get_last_result
|
551
|
+
@statements[sql_key] = nextkey
|
548
552
|
end
|
549
|
-
|
550
|
-
@connection.get_last_result
|
551
|
-
@statements[sql_key] = nextkey
|
553
|
+
@statements[sql_key]
|
552
554
|
end
|
553
|
-
@statements[sql_key]
|
554
555
|
end
|
555
556
|
|
556
557
|
# Connects to a PostgreSQL server and sets up the adapter depending on the
|
557
558
|
# connected server's characteristics.
|
558
559
|
def connect
|
559
|
-
@connection = PG
|
560
|
-
|
560
|
+
@connection = PG.connect(@connection_parameters)
|
561
561
|
configure_connection
|
562
|
-
|
563
|
-
|
564
|
-
raise ActiveRecord::NoDatabaseError.new(error.message, error)
|
565
|
-
else
|
566
|
-
raise
|
567
|
-
end
|
562
|
+
add_pg_encoders
|
563
|
+
add_pg_decoders
|
568
564
|
end
|
569
565
|
|
570
566
|
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
@@ -575,17 +571,29 @@ module ActiveRecord
|
|
575
571
|
end
|
576
572
|
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
577
573
|
|
574
|
+
variables = @config.fetch(:variables, {}).stringify_keys
|
575
|
+
|
576
|
+
# If using Active Record's time zone support configure the connection to return
|
577
|
+
# TIMESTAMP WITH ZONE types in UTC.
|
578
|
+
unless variables["timezone"]
|
579
|
+
if ActiveRecord::Base.default_timezone == :utc
|
580
|
+
variables["timezone"] = "UTC"
|
581
|
+
elsif @local_tz
|
582
|
+
variables["timezone"] = @local_tz
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
578
586
|
# SET statements from :variables config hash
|
579
|
-
#
|
580
|
-
variables = @config[:variables] || {}
|
587
|
+
# https://www.postgresql.org/docs/current/static/sql-set.html
|
581
588
|
variables.map do |k, v|
|
582
|
-
if v ==
|
589
|
+
if v == ":default" || v == :default
|
583
590
|
# Sets the value to the global or compile default
|
584
|
-
execute("SET
|
591
|
+
execute("SET #{k} TO DEFAULT", "SCHEMA")
|
585
592
|
elsif !v.nil?
|
586
|
-
execute("SET
|
593
|
+
execute("SET #{k} TO #{quote(v)}", "SCHEMA")
|
587
594
|
end
|
588
595
|
end
|
596
|
+
|
589
597
|
end
|
590
598
|
|
591
599
|
def last_insert_id_result(sequence_name) #:nodoc:
|
@@ -622,13 +630,111 @@ module ActiveRecord
|
|
622
630
|
end_sql
|
623
631
|
end
|
624
632
|
|
625
|
-
def extract_table_ref_from_insert_sql(sql)
|
633
|
+
def extract_table_ref_from_insert_sql(sql)
|
626
634
|
sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
|
627
635
|
$1.strip if $1
|
628
636
|
end
|
629
637
|
|
638
|
+
def arel_visitor
|
639
|
+
Arel::Visitors::PostgreSQL.new(self)
|
640
|
+
end
|
641
|
+
|
642
|
+
def build_statement_pool
|
643
|
+
StatementPool.new(@connection, self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
644
|
+
end
|
645
|
+
|
646
|
+
|
647
|
+
def can_perform_case_insensitive_comparison_for?(column)
|
648
|
+
@case_insensitive_cache ||= {}
|
649
|
+
@case_insensitive_cache[column.sql_type] ||= begin
|
650
|
+
sql = <<~SQL
|
651
|
+
SELECT exists(
|
652
|
+
SELECT * FROM pg_proc
|
653
|
+
WHERE proname = 'lower'
|
654
|
+
AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
|
655
|
+
) OR exists(
|
656
|
+
SELECT * FROM pg_proc
|
657
|
+
INNER JOIN pg_cast
|
658
|
+
ON ARRAY[casttarget]::oidvector = proargtypes
|
659
|
+
WHERE proname = 'lower'
|
660
|
+
AND castsource = #{quote column.sql_type}::regtype
|
661
|
+
)
|
662
|
+
SQL
|
663
|
+
execute_and_clear(sql, "SCHEMA", []) do |result|
|
664
|
+
result.getvalue(0, 0)
|
665
|
+
end
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
def add_pg_encoders
|
670
|
+
map = PG::TypeMapByClass.new
|
671
|
+
map[Integer] = PG::TextEncoder::Integer.new
|
672
|
+
map[TrueClass] = PG::TextEncoder::Boolean.new
|
673
|
+
map[FalseClass] = PG::TextEncoder::Boolean.new
|
674
|
+
@connection.type_map_for_queries = map
|
675
|
+
end
|
676
|
+
|
677
|
+
def update_typemap_for_default_timezone
|
678
|
+
if @default_timezone != ActiveRecord::Base.default_timezone && @timestamp_decoder
|
679
|
+
decoder_class = ActiveRecord::Base.default_timezone == :utc ?
|
680
|
+
PG::TextDecoder::TimestampUtc :
|
681
|
+
PG::TextDecoder::TimestampWithoutTimeZone
|
682
|
+
|
683
|
+
@timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
|
684
|
+
@connection.type_map_for_results.add_coder(@timestamp_decoder)
|
685
|
+
@default_timezone = ActiveRecord::Base.default_timezone
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
|
690
|
+
def add_pg_decoders
|
691
|
+
@default_timezone = nil
|
692
|
+
@timestamp_decoder = nil
|
693
|
+
|
694
|
+
coders_by_name = {
|
695
|
+
"int2" => PG::TextDecoder::Integer,
|
696
|
+
"int4" => PG::TextDecoder::Integer,
|
697
|
+
"int8" => PG::TextDecoder::Integer,
|
698
|
+
"oid" => PG::TextDecoder::Integer,
|
699
|
+
"float4" => PG::TextDecoder::Float,
|
700
|
+
"float8" => PG::TextDecoder::Float,
|
701
|
+
"bool" => PG::TextDecoder::Boolean,
|
702
|
+
}
|
703
|
+
|
704
|
+
if defined?(PG::TextDecoder::TimestampUtc)
|
705
|
+
# Use native PG encoders available since pg-1.1
|
706
|
+
coders_by_name["timestamp"] = PG::TextDecoder::TimestampUtc
|
707
|
+
coders_by_name["timestamptz"] = PG::TextDecoder::TimestampWithTimeZone
|
708
|
+
end
|
709
|
+
|
710
|
+
known_coder_types = coders_by_name.keys.map { |n| quote(n) }
|
711
|
+
query = <<~SQL % known_coder_types.join(", ")
|
712
|
+
SELECT t.oid, t.typname
|
713
|
+
FROM pg_type as t
|
714
|
+
WHERE t.typname IN (%s)
|
715
|
+
SQL
|
716
|
+
coders = execute_and_clear(query, "SCHEMA", []) do |result|
|
717
|
+
result
|
718
|
+
.map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
|
719
|
+
.compact
|
720
|
+
end
|
721
|
+
|
722
|
+
map = PG::TypeMapByOid.new
|
723
|
+
coders.each { |coder| map.add_coder(coder) }
|
724
|
+
@connection.type_map_for_results = map
|
725
|
+
|
726
|
+
# extract timestamp decoder for use in update_typemap_for_default_timezone
|
727
|
+
@timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
|
728
|
+
update_typemap_for_default_timezone
|
729
|
+
end
|
730
|
+
|
731
|
+
def construct_coder(row, coder_class)
|
732
|
+
return unless coder_class
|
733
|
+
coder_class.new(oid: row["oid"].to_i, name: row["typname"])
|
734
|
+
end
|
735
|
+
|
630
736
|
def create_table_definition(*args) # :nodoc:
|
631
|
-
Redshift::TableDefinition.new(*args)
|
737
|
+
Redshift::TableDefinition.new(self, *args)
|
632
738
|
end
|
633
739
|
end
|
634
740
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord6-redshift-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nancy Foen
|
@@ -11,22 +11,22 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2022-02-04 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: pg
|
18
18
|
requirement: !ruby/object:Gem::Requirement
|
19
19
|
requirements:
|
20
|
-
- - "
|
20
|
+
- - "~>"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '0
|
22
|
+
version: '1.0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
|
-
- - "
|
27
|
+
- - "~>"
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '0
|
29
|
+
version: '1.0'
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: activerecord
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -34,9 +34,6 @@ dependencies:
|
|
34
34
|
- - "~>"
|
35
35
|
- !ruby/object:Gem::Version
|
36
36
|
version: '6.0'
|
37
|
-
- - ">="
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: 6.0.0
|
40
37
|
type: :runtime
|
41
38
|
prerelease: false
|
42
39
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -44,11 +41,8 @@ dependencies:
|
|
44
41
|
- - "~>"
|
45
42
|
- !ruby/object:Gem::Version
|
46
43
|
version: '6.0'
|
47
|
-
|
48
|
-
|
49
|
-
version: 6.0.0
|
50
|
-
description: Amazon Redshift _makeshift_ adapter for ActiveRecord 6.
|
51
|
-
email: fantast.d@gmail.com
|
44
|
+
description: Amazon Redshift adapter for ActiveRecord 6.x.
|
45
|
+
email: contact@quent.in
|
52
46
|
executables: []
|
53
47
|
extensions: []
|
54
48
|
extra_rdoc_files: []
|
@@ -84,15 +78,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
78
|
requirements:
|
85
79
|
- - ">="
|
86
80
|
- !ruby/object:Gem::Version
|
87
|
-
version:
|
81
|
+
version: '3.0'
|
88
82
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
83
|
requirements:
|
90
84
|
- - ">="
|
91
85
|
- !ruby/object:Gem::Version
|
92
86
|
version: '0'
|
93
87
|
requirements: []
|
94
|
-
|
95
|
-
rubygems_version: 2.5.2.3
|
88
|
+
rubygems_version: 3.0.3.1
|
96
89
|
signing_key:
|
97
90
|
specification_version: 4
|
98
91
|
summary: Amazon Redshift adapter for ActiveRecord
|