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.
- checksums.yaml +4 -4
- data/lib/active_record/connection_adapters/redshift_7_0/database_statements.rb +5 -0
- data/lib/active_record/connection_adapters/redshift_7_0/schema_statements.rb +4 -4
- data/lib/active_record/connection_adapters/redshift_7_0_adapter.rb +3 -6
- data/lib/active_record/connection_adapters/redshift_7_1/schema_statements.rb +4 -4
- data/lib/active_record/connection_adapters/redshift_7_1_adapter.rb +0 -10
- data/lib/active_record/connection_adapters/redshift_7_2/schema_statements.rb +4 -4
- data/lib/active_record/connection_adapters/redshift_7_2_adapter.rb +0 -10
- data/lib/active_record/connection_adapters/redshift_8_0/schema_statements.rb +8 -7
- data/lib/active_record/connection_adapters/redshift_8_0_adapter.rb +17 -10
- data/lib/active_record/connection_adapters/redshift_8_1/array_parser.rb +92 -0
- data/lib/active_record/connection_adapters/redshift_8_1/column.rb +20 -0
- data/lib/active_record/connection_adapters/redshift_8_1/database_statements.rb +181 -0
- data/lib/active_record/connection_adapters/redshift_8_1/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/redshift_8_1/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/redshift_8_1/oid/json.rb +41 -0
- data/lib/active_record/connection_adapters/redshift_8_1/oid/jsonb.rb +25 -0
- data/lib/active_record/connection_adapters/redshift_8_1/oid/type_map_initializer.rb +62 -0
- data/lib/active_record/connection_adapters/redshift_8_1/oid.rb +17 -0
- data/lib/active_record/connection_adapters/redshift_8_1/quoting.rb +164 -0
- data/lib/active_record/connection_adapters/redshift_8_1/referential_integrity.rb +17 -0
- data/lib/active_record/connection_adapters/redshift_8_1/schema_definitions.rb +70 -0
- data/lib/active_record/connection_adapters/redshift_8_1/schema_dumper.rb +17 -0
- data/lib/active_record/connection_adapters/redshift_8_1/schema_statements.rb +423 -0
- data/lib/active_record/connection_adapters/redshift_8_1/type_metadata.rb +43 -0
- data/lib/active_record/connection_adapters/redshift_8_1/utils.rb +81 -0
- data/lib/active_record/connection_adapters/redshift_8_1_adapter.rb +858 -0
- data/lib/active_record/connection_adapters/redshift_adapter.rb +3 -1
- metadata +22 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 338dd0abbf4008aa2bcb146fc2200c1ec640d505b8960d1c606dccea6ecae153
|
|
4
|
+
data.tar.gz: 1310ecbf60091d6838582ec512de9edff196551a21219336128f432a6146aa8b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 =
|
|
224
|
-
result = serial_sequence(table_name, pk
|
|
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
|
|
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(
|
|
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
|
|
655
|
-
|
|
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 =
|
|
224
|
-
result = serial_sequence(table_name, pk
|
|
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
|
|
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(
|
|
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 =
|
|
224
|
-
result = serial_sequence(table_name, pk
|
|
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
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
224
|
-
result = serial_sequence(table_name, pk
|
|
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
|
|
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(
|
|
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
|