activerecord6-redshift-adapter 1.1.2 → 1.3.0
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 +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
|