activerecord-redshift-adapter 8.0.0.beta2 → 8.1.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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/lib/active_record/connection_adapters/redshift_7_0/database_statements.rb +5 -0
  3. data/lib/active_record/connection_adapters/redshift_7_0/schema_statements.rb +4 -4
  4. data/lib/active_record/connection_adapters/redshift_7_0_adapter.rb +3 -6
  5. data/lib/active_record/connection_adapters/redshift_7_1/schema_statements.rb +4 -4
  6. data/lib/active_record/connection_adapters/redshift_7_1_adapter.rb +0 -10
  7. data/lib/active_record/connection_adapters/redshift_7_2/schema_statements.rb +4 -4
  8. data/lib/active_record/connection_adapters/redshift_7_2_adapter.rb +0 -10
  9. data/lib/active_record/connection_adapters/redshift_8_0/schema_statements.rb +8 -7
  10. data/lib/active_record/connection_adapters/redshift_8_0_adapter.rb +17 -10
  11. data/lib/active_record/connection_adapters/redshift_8_1/array_parser.rb +92 -0
  12. data/lib/active_record/connection_adapters/redshift_8_1/column.rb +20 -0
  13. data/lib/active_record/connection_adapters/redshift_8_1/database_statements.rb +181 -0
  14. data/lib/active_record/connection_adapters/redshift_8_1/oid/date_time.rb +36 -0
  15. data/lib/active_record/connection_adapters/redshift_8_1/oid/decimal.rb +15 -0
  16. data/lib/active_record/connection_adapters/redshift_8_1/oid/json.rb +41 -0
  17. data/lib/active_record/connection_adapters/redshift_8_1/oid/jsonb.rb +25 -0
  18. data/lib/active_record/connection_adapters/redshift_8_1/oid/type_map_initializer.rb +62 -0
  19. data/lib/active_record/connection_adapters/redshift_8_1/oid.rb +17 -0
  20. data/lib/active_record/connection_adapters/redshift_8_1/quoting.rb +164 -0
  21. data/lib/active_record/connection_adapters/redshift_8_1/referential_integrity.rb +17 -0
  22. data/lib/active_record/connection_adapters/redshift_8_1/schema_definitions.rb +70 -0
  23. data/lib/active_record/connection_adapters/redshift_8_1/schema_dumper.rb +17 -0
  24. data/lib/active_record/connection_adapters/redshift_8_1/schema_statements.rb +423 -0
  25. data/lib/active_record/connection_adapters/redshift_8_1/type_metadata.rb +43 -0
  26. data/lib/active_record/connection_adapters/redshift_8_1/utils.rb +81 -0
  27. data/lib/active_record/connection_adapters/redshift_8_1_adapter.rb +858 -0
  28. data/lib/active_record/connection_adapters/redshift_adapter.rb +3 -1
  29. metadata +22 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35bf91b5a6116c1055f34e7bb0fc17805e6e693cec22520fc9014010962f2ee3
4
- data.tar.gz: d4c0213bcee62e43faa979d0fdc1080b590f86d511a4b1f53c2ff623622737ce
3
+ metadata.gz: 338dd0abbf4008aa2bcb146fc2200c1ec640d505b8960d1c606dccea6ecae153
4
+ data.tar.gz: 1310ecbf60091d6838582ec512de9edff196551a21219336128f432a6146aa8b
5
5
  SHA512:
6
- metadata.gz: 6075000b038c72d2e516173fa1ee3f29b3c80d2cff8036418f36b919ccc312e8085edfe841394642f906bbe6bf5161ccd6d0c0b42da10af061ce9618425194a1
7
- data.tar.gz: f797bb18c2b7b4732ff0652b6f50b1f26cf0688a2d1c0eca5c2a0e310405af715f2ecfd1f9537b08dfbbbfb95e928eda10ffd6f35790db11eff5ff6e915d1430
6
+ metadata.gz: 77e97c2018f1d33a95cf921174f11569b83e6f1e0dd4b9ef19890b4f1e321e6e56a45385c9ff3db9cf5237bcec95018257c51afbabf17fde57d87be827d0a33f
7
+ data.tar.gz: ab3c6ff497683b3cf53832e59c97738a4fcd94cceec23e8f5f6d5b77d433c2f9a2848f215d65c0bf8d9ab62163ff0ed10cac2d3782569dadf002637259c99bd0
@@ -207,6 +207,11 @@ module ActiveRecord
207
207
  end
208
208
  end
209
209
 
210
+ # Returns the current ID of a table's sequence.
211
+ def last_insert_id_result(sequence_name)
212
+ internal_exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
213
+ end
214
+
210
215
  # Begins a transaction.
211
216
  def begin_db_transaction
212
217
  execute 'BEGIN'
@@ -220,17 +220,17 @@ module ActiveRecord
220
220
  end
221
221
 
222
222
  # Returns the sequence name for a table's primary key or some other specified key.
223
- def default_sequence_name(table_name, pk = nil) # :nodoc:
224
- result = serial_sequence(table_name, pk || 'id')
223
+ def default_sequence_name(table_name, pk = "id") # :nodoc:
224
+ result = serial_sequence(table_name, pk)
225
225
  return nil unless result
226
226
 
227
227
  Utils.extract_schema_qualified_name(result).to_s
228
228
  rescue ActiveRecord::StatementInvalid
229
- Redshift::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
229
+ Redshift::Name.new(nil, "#{table_name}_#{pk}_seq").to_s
230
230
  end
231
231
 
232
232
  def serial_sequence(table, column)
233
- select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA')
233
+ select_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})", 'SCHEMA')
234
234
  end
235
235
 
236
236
  def set_pk_sequence!(table, value); end
@@ -616,10 +616,6 @@ module ActiveRecord
616
616
  end
617
617
  end
618
618
 
619
- def last_insert_id_result(sequence_name) # :nodoc:
620
- exec_query("SELECT currval('#{sequence_name}')", 'SQL')
621
- end
622
-
623
619
  # Returns the list of a table's column names, data types, and default values.
624
620
  #
625
621
  # The underlying query is roughly:
@@ -651,8 +647,9 @@ module ActiveRecord
651
647
  end
652
648
 
653
649
  def extract_table_ref_from_insert_sql(sql)
654
- sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
655
- Regexp.last_match(1)&.strip
650
+ if sql =~ /into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im
651
+ $1.delete('"').strip
652
+ end
656
653
  end
657
654
 
658
655
  def arel_visitor
@@ -220,17 +220,17 @@ module ActiveRecord
220
220
  end
221
221
 
222
222
  # Returns the sequence name for a table's primary key or some other specified key.
223
- def default_sequence_name(table_name, pk = nil) # :nodoc:
224
- result = serial_sequence(table_name, pk || 'id')
223
+ def default_sequence_name(table_name, pk = 'id') # :nodoc:
224
+ result = serial_sequence(table_name, pk)
225
225
  return nil unless result
226
226
 
227
227
  Utils.extract_schema_qualified_name(result).to_s
228
228
  rescue ActiveRecord::StatementInvalid
229
- Redshift::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
229
+ Redshift::Name.new(nil, "#{table_name}_#{pk}_seq").to_s
230
230
  end
231
231
 
232
232
  def serial_sequence(table, column)
233
- select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA')
233
+ select_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})", 'SCHEMA')
234
234
  end
235
235
 
236
236
  def set_pk_sequence!(table, value); end
@@ -689,11 +689,6 @@ module ActiveRecord
689
689
  end
690
690
  end
691
691
 
692
- def last_insert_id_result(sequence_name)
693
- # :nodoc:
694
- exec_query("SELECT currval('#{sequence_name}')", 'SQL')
695
- end
696
-
697
692
  # Returns the list of a table's column names, data types, and default values.
698
693
  #
699
694
  # The underlying query is roughly:
@@ -725,11 +720,6 @@ module ActiveRecord
725
720
  END_SQL
726
721
  end
727
722
 
728
- def extract_table_ref_from_insert_sql(sql)
729
- sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
730
- Regexp.last_match(1)&.strip
731
- end
732
-
733
723
  def arel_visitor
734
724
  Arel::Visitors::PostgreSQL.new(self)
735
725
  end
@@ -220,17 +220,17 @@ module ActiveRecord
220
220
  end
221
221
 
222
222
  # Returns the sequence name for a table's primary key or some other specified key.
223
- def default_sequence_name(table_name, pk = nil) # :nodoc:
224
- result = serial_sequence(table_name, pk || 'id')
223
+ def default_sequence_name(table_name, pk = 'id') # :nodoc:
224
+ result = serial_sequence(table_name, pk)
225
225
  return nil unless result
226
226
 
227
227
  Utils.extract_schema_qualified_name(result).to_s
228
228
  rescue ActiveRecord::StatementInvalid
229
- Redshift::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
229
+ Redshift::Name.new(nil, "#{table_name}_#{pk}_seq").to_s
230
230
  end
231
231
 
232
232
  def serial_sequence(table, column)
233
- select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA')
233
+ select_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})".tap { puts _1 }, 'SCHEMA')
234
234
  end
235
235
 
236
236
  def set_pk_sequence!(table, value); end
@@ -689,11 +689,6 @@ module ActiveRecord
689
689
  end
690
690
  end
691
691
 
692
- def last_insert_id_result(sequence_name)
693
- # :nodoc:
694
- exec_query("SELECT currval('#{sequence_name}')", 'SQL')
695
- end
696
-
697
692
  # Returns the list of a table's column names, data types, and default values.
698
693
  #
699
694
  # The underlying query is roughly:
@@ -725,11 +720,6 @@ module ActiveRecord
725
720
  END_SQL
726
721
  end
727
722
 
728
- def extract_table_ref_from_insert_sql(sql)
729
- sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
730
- Regexp.last_match(1)&.strip
731
- end
732
-
733
723
  def arel_visitor
734
724
  Arel::Visitors::PostgreSQL.new(self)
735
725
  end
@@ -152,12 +152,13 @@ module ActiveRecord
152
152
  default_value = extract_value_from_default(default)
153
153
  type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
154
154
  default_function = extract_default_function(default_value, default)
155
- new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function)
155
+ cast_type = get_oid_type(oid.to_i, fmod.to_i, column_name, type)
156
+ new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, cast_type: cast_type)
156
157
  end
157
158
  end
158
159
 
159
- def new_column(name, default, sql_type_metadata = nil, null = true, _table_name = nil, default_function = nil) # :nodoc:
160
- RedshiftColumn.new(name, default, sql_type_metadata, null, default_function)
160
+ def new_column(name, default, sql_type_metadata = nil, null = true, _table_name = nil, default_function = nil, cast_type: nil) # :nodoc:
161
+ RedshiftColumn.new(name, default, sql_type_metadata, null, default_function, cast_type: cast_type)
161
162
  end
162
163
 
163
164
  # Returns the current database name.
@@ -220,17 +221,17 @@ module ActiveRecord
220
221
  end
221
222
 
222
223
  # Returns the sequence name for a table's primary key or some other specified key.
223
- def default_sequence_name(table_name, pk = nil) # :nodoc:
224
- result = serial_sequence(table_name, pk || 'id')
224
+ def default_sequence_name(table_name, pk = 'id') # :nodoc:
225
+ result = serial_sequence(table_name, pk)
225
226
  return nil unless result
226
227
 
227
228
  Utils.extract_schema_qualified_name(result).to_s
228
229
  rescue ActiveRecord::StatementInvalid
229
- Redshift::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
230
+ Redshift::Name.new(nil, "#{table_name}_#{pk}_seq").to_s
230
231
  end
231
232
 
232
233
  def serial_sequence(table, column)
233
- select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA')
234
+ select_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})".tap { puts _1 }, 'SCHEMA')
234
235
  end
235
236
 
236
237
  def set_pk_sequence!(table, value); end
@@ -90,6 +90,13 @@ module ActiveRecord
90
90
  include Redshift::SchemaStatements
91
91
  include Redshift::DatabaseStatements
92
92
 
93
+ # Compatibility: in_transaction? is defined in PostgreSQLAdapter but not in AbstractAdapter.
94
+ # Since RedshiftAdapter inherits from AbstractAdapter (not PostgreSQLAdapter),
95
+ # we need to provide this method. It's used for prepared statement cache handling.
96
+ def in_transaction? # :nodoc:
97
+ open_transactions > 0
98
+ end
99
+
93
100
  def schema_creation # :nodoc:
94
101
  Redshift::SchemaCreation.new self
95
102
  end
@@ -360,6 +367,16 @@ module ActiveRecord
360
367
  end
361
368
  end
362
369
 
370
+ # Rails 8.1 deprecated AbstractAdapter#check_if_write_query
371
+ # Implement it here for compatibility if it doesn't exist in the parent class
372
+ unless AbstractAdapter.method_defined?(:check_if_write_query)
373
+ def check_if_write_query(sql)
374
+ return unless preventing_writes? && write_query?(sql)
375
+
376
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
377
+ end
378
+ end
379
+
363
380
  class << self
364
381
  def initialize_type_map(m)
365
382
  # :nodoc:
@@ -688,11 +705,6 @@ module ActiveRecord
688
705
  end
689
706
  end
690
707
 
691
- def last_insert_id_result(sequence_name)
692
- # :nodoc:
693
- exec_query("SELECT currval('#{sequence_name}')", 'SQL')
694
- end
695
-
696
708
  # Returns the list of a table's column names, data types, and default values.
697
709
  #
698
710
  # The underlying query is roughly:
@@ -724,11 +736,6 @@ module ActiveRecord
724
736
  END_SQL
725
737
  end
726
738
 
727
- def extract_table_ref_from_insert_sql(sql)
728
- sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
729
- Regexp.last_match(1)&.strip
730
- end
731
-
732
739
  def arel_visitor
733
740
  Arel::Visitors::PostgreSQL.new(self)
734
741
  end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module ArrayParser # :nodoc:
7
+ DOUBLE_QUOTE = '"'
8
+ BACKSLASH = '\\'
9
+ COMMA = ','
10
+ BRACKET_OPEN = '{'
11
+ BRACKET_CLOSE = '}'
12
+
13
+ def parse_pg_array(string) # :nodoc:
14
+ local_index = 0
15
+ array = []
16
+ while local_index < string.length
17
+ case string[local_index]
18
+ when BRACKET_OPEN
19
+ local_index, array = parse_array_contents(array, string, local_index + 1)
20
+ when BRACKET_CLOSE
21
+ return array
22
+ end
23
+ local_index += 1
24
+ end
25
+
26
+ array
27
+ end
28
+
29
+ private
30
+
31
+ def parse_array_contents(array, string, index)
32
+ is_escaping = false
33
+ is_quoted = false
34
+ was_quoted = false
35
+ current_item = ''
36
+
37
+ local_index = index
38
+ while local_index
39
+ token = string[local_index]
40
+ if is_escaping
41
+ current_item << token
42
+ is_escaping = false
43
+ elsif is_quoted
44
+ case token
45
+ when DOUBLE_QUOTE
46
+ is_quoted = false
47
+ was_quoted = true
48
+ when BACKSLASH
49
+ is_escaping = true
50
+ else
51
+ current_item << token
52
+ end
53
+ else
54
+ case token
55
+ when BACKSLASH
56
+ is_escaping = true
57
+ when COMMA
58
+ add_item_to_array(array, current_item, was_quoted)
59
+ current_item = ''
60
+ was_quoted = false
61
+ when DOUBLE_QUOTE
62
+ is_quoted = true
63
+ when BRACKET_OPEN
64
+ internal_items = []
65
+ local_index, internal_items = parse_array_contents(internal_items, string, local_index + 1)
66
+ array.push(internal_items)
67
+ when BRACKET_CLOSE
68
+ add_item_to_array(array, current_item, was_quoted)
69
+ return local_index, array
70
+ else
71
+ current_item << token
72
+ end
73
+ end
74
+
75
+ local_index += 1
76
+ end
77
+ [local_index, array]
78
+ end
79
+
80
+ def add_item_to_array(array, current_item, quoted)
81
+ return if !quoted && current_item.empty?
82
+
83
+ if !quoted && current_item == 'NULL'
84
+ array.push nil
85
+ else
86
+ array.push current_item
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ class RedshiftColumn < Column # :nodoc:
6
+ delegate :oid, :fmod, to: :sql_type_metadata
7
+
8
+ # Required for Rails 6.1, see https://github.com/rails/rails/pull/41756
9
+ mattr_reader :array, default: false
10
+ alias array? array
11
+
12
+ # Rails 8.1 changed Column#initialize signature to require cast_type as second argument:
13
+ # initialize(name, cast_type, default, sql_type_metadata, null, ...)
14
+ def initialize(name, default, sql_type_metadata, null = true, default_function = nil, cast_type: nil, **)
15
+ cast_type ||= ActiveRecord::Type::Value.new
16
+ super(name, cast_type, default, sql_type_metadata, null, default_function)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module DatabaseStatements
7
+ def explain(arel, binds = [], options = [])
8
+ sql = build_explain_clause(options) + " " + to_sql(arel, binds)
9
+ result = internal_exec_query(sql, "EXPLAIN", binds)
10
+ PostgreSQL::ExplainPrettyPrinter.new.pp(result)
11
+ end
12
+
13
+ # Queries the database and returns the results in an Array-like object
14
+ def query(sql, name = nil) # :nodoc:
15
+ mark_transaction_written_if_write(sql)
16
+
17
+ log(sql, name) do
18
+ with_raw_connection do |conn|
19
+ result = conn.async_exec(sql).map_types!(@type_map_for_results).values
20
+ verified!
21
+ result
22
+ end
23
+ end
24
+ end
25
+
26
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
27
+ :close, :declare, :fetch, :move, :set, :show
28
+ ) # :nodoc:
29
+ private_constant :READ_QUERY
30
+
31
+ def write_query?(sql) # :nodoc:
32
+ !READ_QUERY.match?(sql)
33
+ rescue ArgumentError # Invalid encoding
34
+ !READ_QUERY.match?(sql.b)
35
+ end
36
+
37
+ # Executes an SQL statement, returning a PG::Result object on success
38
+ # or raising a PG::Error exception otherwise.
39
+ #
40
+ # Setting +allow_retry+ to true causes the db to reconnect and retry
41
+ # executing the SQL statement in case of a connection-related exception.
42
+ # This option should only be enabled for known idempotent queries.
43
+ #
44
+ # Note: the PG::Result object is manually memory managed; if you don't
45
+ # need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
46
+ def execute(...) # :nodoc:
47
+ super
48
+ ensure
49
+ @notice_receiver_sql_warnings = []
50
+ end
51
+
52
+ def raw_execute(sql, name, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true)
53
+ type_casted_binds = type_casted_binds(binds)
54
+ log(sql, name, binds, type_casted_binds, async: async) do
55
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
56
+ result = conn.async_exec(sql)
57
+ verified!
58
+ handle_warnings(result)
59
+ result
60
+ end
61
+ end
62
+ end
63
+
64
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
65
+ execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
66
+ types = {}
67
+ fields = result.fields
68
+ fields.each_with_index do |fname, i|
69
+ ftype = result.ftype i
70
+ fmod = result.fmod i
71
+ types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
72
+ end
73
+ build_result(columns: fields, rows: result.values, column_types: types)
74
+ end
75
+ end
76
+
77
+ def exec_delete(sql, name = nil, binds = []) # :nodoc:
78
+ execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
79
+ end
80
+ alias :exec_update :exec_delete
81
+
82
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
83
+ if use_insert_returning? || pk == false
84
+ super
85
+ else
86
+ result = internal_exec_query(sql, name, binds)
87
+ unless sequence_name
88
+ table_ref = extract_table_ref_from_insert_sql(sql)
89
+ if table_ref
90
+ pk = primary_key(table_ref) if pk.nil?
91
+ pk = suppress_composite_primary_key(pk)
92
+ sequence_name = default_sequence_name(table_ref, pk)
93
+ end
94
+ return result unless sequence_name
95
+ end
96
+ last_insert_id_result(sequence_name)
97
+ end
98
+ end
99
+
100
+ # Begins a transaction.
101
+ def begin_db_transaction
102
+ internal_execute('BEGIN', allow_retry: true, materialize_transactions: false)
103
+ end
104
+
105
+ def begin_isolated_db_transaction(isolation)
106
+ begin_db_transaction
107
+ internal_execute("SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", allow_retry: true, materialize_transactions: false)
108
+ end
109
+
110
+ # Commits a transaction.
111
+ def commit_db_transaction
112
+ internal_execute('COMMIT', allow_retry: false, materialize_transactions: true)
113
+ end
114
+
115
+ # Aborts a transaction.
116
+ def exec_rollback_db_transaction
117
+ internal_execute('ROLLBACK', allow_retry: false, materialize_transactions: true)
118
+ end
119
+
120
+ def exec_restart_db_transaction # :nodoc:
121
+ cancel_any_running_query
122
+ exec_rollback_db_transaction
123
+ begin_db_transaction
124
+ end
125
+
126
+ # From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
127
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
128
+ private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
129
+
130
+ def high_precision_current_timestamp
131
+ HIGH_PRECISION_CURRENT_TIMESTAMP
132
+ end
133
+
134
+ def build_explain_clause(options = [])
135
+ return "EXPLAIN" if options.empty?
136
+
137
+ "EXPLAIN (#{options.join(", ").upcase})"
138
+ end
139
+
140
+ private
141
+
142
+ IDLE_TRANSACTION_STATUSES = [PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR]
143
+ private_constant :IDLE_TRANSACTION_STATUSES
144
+
145
+ def cancel_any_running_query
146
+ return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
147
+
148
+ @raw_connection.cancel
149
+ @raw_connection.block
150
+ rescue PG::Error
151
+ end
152
+
153
+ # Returns the current ID of a table's sequence.
154
+ def last_insert_id_result(sequence_name)
155
+ internal_exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
156
+ end
157
+
158
+ def returning_column_values(result)
159
+ result.rows.first
160
+ end
161
+
162
+ def suppress_composite_primary_key(pk)
163
+ pk unless pk.is_a?(Array)
164
+ end
165
+
166
+ def handle_warnings(sql)
167
+ @notice_receiver_sql_warnings.each do |warning|
168
+ next if warning_ignored?(warning)
169
+
170
+ warning.sql = sql
171
+ ActiveRecord.db_warnings_action.call(warning)
172
+ end
173
+ end
174
+
175
+ def warning_ignored?(warning)
176
+ ["WARNING", "ERROR", "FATAL", "PANIC"].exclude?(warning.level) || super
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module OID # :nodoc:
7
+ class DateTime < Type::DateTime # :nodoc:
8
+ def type_cast_for_database(value)
9
+ if has_precision? && value.acts_like?(:time) && value.year <= 0
10
+ bce_year = format('%04d', -value.year + 1)
11
+ "#{super.sub(/^-?\d+/, bce_year)} BC"
12
+ else
13
+ super
14
+ end
15
+ end
16
+
17
+ def cast_value(value)
18
+ if value.is_a?(::String)
19
+ case value
20
+ when 'infinity' then ::Float::INFINITY
21
+ when '-infinity' then -::Float::INFINITY
22
+ when / BC$/
23
+ astronomical_year = format('%04d', -value[/^\d+/].to_i + 1)
24
+ super(value.sub(/ BC$/, '').sub(/^\d+/, astronomical_year))
25
+ else
26
+ super
27
+ end
28
+ else
29
+ value
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module OID # :nodoc:
7
+ class Decimal < Type::Decimal # :nodoc:
8
+ def infinity(options = {})
9
+ BigDecimal('Infinity') * (options[:negative] ? -1 : 1)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module OID # :nodoc:
7
+ class Json < Type::Value # :nodoc:
8
+ include ActiveModel::Type::Helpers::Mutable
9
+
10
+ def type
11
+ :json
12
+ end
13
+
14
+ def type_cast_from_database(value)
15
+ if value.is_a?(::String)
16
+ begin
17
+ ::ActiveSupport::JSON.decode(value)
18
+ rescue StandardError
19
+ nil
20
+ end
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def type_cast_for_database(value)
27
+ if value.is_a?(::Array) || value.is_a?(::Hash)
28
+ ::ActiveSupport::JSON.encode(value)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def accessor
35
+ ActiveRecord::Store::StringKeyedHashAccessor
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Redshift
6
+ module OID # :nodoc:
7
+ class Jsonb < Json # :nodoc:
8
+ def type
9
+ :jsonb
10
+ end
11
+
12
+ def changed_in_place?(raw_old_value, new_value)
13
+ # Postgres does not preserve insignificant whitespaces when
14
+ # roundtripping jsonb columns. This causes some false positives for
15
+ # the comparison here. Therefore, we need to parse and re-dump the
16
+ # raw value here to ensure the insignificant whitespaces are
17
+ # consistent with our encoder's output.
18
+ raw_old_value = type_cast_for_database(type_cast_from_database(raw_old_value))
19
+ super(raw_old_value, new_value)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end