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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5830bb316b6a3021508d6542b09478a0b0c3ce7a
4
- data.tar.gz: eb66565b485ab9464f93fe5f2e392a37a64d2805
2
+ SHA256:
3
+ metadata.gz: 95fe63dabddcdce38d3cf4e84d8a891a682ddcbd5884ec19bd0c1907693d1773
4
+ data.tar.gz: 00c59c4c3f97f14c08a9f140399e9a698d19b7ad5cd18d65e6291c23f7fb9486
5
5
  SHA512:
6
- metadata.gz: 69d0323991a44986cef4b87d9268eaeff017c46dfd83cd6785753353c18900cc0dedde17539ef4c99e476d10f4b61e262aea755aaea8acae9070763d61f8aa79
7
- data.tar.gz: 819f6b941172c242926a3fc6229fe4b4d12742c89115178cfa4ae4d929e5eb2eea0cee773b852126074b2d26993db6e2faef0b99ecf6aafec75500bd0e49e41a
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
- def initialize(name, default, sql_type_metadata, null = true, default_function = nil)
7
- super name, default, sql_type_metadata, null, default_function
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 = to_sql(arel, 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 = to_sql(arel, [])
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(sql, name = nil, binds = [])
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
@@ -3,7 +3,7 @@ module ActiveRecord
3
3
  module Redshift
4
4
  module OID # :nodoc:
5
5
  class Decimal < Type::Decimal # :nodoc:
6
- def infinity(options = {})
6
+ def infinity(**options)
7
7
  BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1)
8
8
  end
9
9
  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(name, type)
58
- Redshift::ColumnDefinition.new name, type
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
- def visit_ColumnDefinition(o)
8
- o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
9
- super
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
- def add_column_options!(sql, options)
13
- column = options.fetch(:column) { return super }
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 = {}) #:nodoc:
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 = {}) #:nodoc:
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 = {}) #:nodoc:
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 = nil, precision = nil, scale = nil)
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: "float" },
85
+ float: { name: "decimal" },
82
86
  decimal: { name: "decimal" },
83
87
  datetime: { name: "timestamp" },
84
- time: { name: "time" },
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
- while @max <= cache.size
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::Connection::CONNECTION_OK
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
- log(sql, name, binds) { @connection.async_exec(sql, []) }
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
- stmt_key = prepare_statement(sql)
505
- type_casted_binds = binds.map { |col, val|
506
- [col, type_cast(val, col)]
507
- }
486
+ materialize_transactions
487
+ update_typemap_for_default_timezone
508
488
 
509
- log(sql, name, type_casted_binds, stmt_key) do
510
- @connection.exec_prepared(stmt_key, type_casted_binds.map { |_, val| val })
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
- pgerror = e.original_exception
514
-
515
- # Get the PG code for the failure. Annoyingly, the code for
516
- # prepared statements whose return value may have changed is
517
- # FEATURE_NOT_SUPPORTED. Check here for more details:
518
- # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
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
- raise e
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
- 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)
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
- # Clear the queue
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::Connection.connect(@connection_parameters)
560
-
560
+ @connection = PG.connect(@connection_parameters)
561
561
  configure_connection
562
- rescue ::PG::Error => error
563
- if error.message.include?("does not exist")
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
- # http://www.postgresql.org/docs/8.3/static/sql-set.html
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 == ':default' || v == :default
589
+ if v == ":default" || v == :default
583
590
  # Sets the value to the global or compile default
584
- execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA')
591
+ execute("SET #{k} TO DEFAULT", "SCHEMA")
585
592
  elsif !v.nil?
586
- execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA')
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) # :nodoc:
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.1.2
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: 2019-09-05 00:00:00.000000000 Z
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.18'
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.18'
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
- - !ruby/object:Gem::Version
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: 2.2.2
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
- rubyforge_project:
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