activerecord-redshift-adapter 8.0.0 → 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_8_0/schema_statements.rb +4 -3
- data/lib/active_record/connection_adapters/redshift_8_0_adapter.rb +17 -0
- 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
|
|
@@ -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.
|
|
@@ -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:
|
|
@@ -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
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module Redshift
|
|
6
|
+
module OID # :nodoc:
|
|
7
|
+
# This class uses the data from PostgreSQL pg_type table to build
|
|
8
|
+
# the OID -> Type mapping.
|
|
9
|
+
# - OID is an integer representing the type.
|
|
10
|
+
# - Type is an OID::Type object.
|
|
11
|
+
# This class has side effects on the +store+ passed during initialization.
|
|
12
|
+
class TypeMapInitializer # :nodoc:
|
|
13
|
+
def initialize(store, run_complex_types = true)
|
|
14
|
+
@store = store
|
|
15
|
+
@run_complex_types = run_complex_types
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def run(records)
|
|
19
|
+
records
|
|
20
|
+
.reject { |row| @store.key? row['oid'].to_i }
|
|
21
|
+
.select { |row| @store.key? row['typname'] }
|
|
22
|
+
.each { |row| register_mapped_type(row) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def register_mapped_type(row)
|
|
28
|
+
alias_type row['oid'], row['typname']
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def register(oid, oid_type = nil, &block)
|
|
32
|
+
oid = assert_valid_registration(oid, oid_type || block)
|
|
33
|
+
if block_given?
|
|
34
|
+
@store.register_type(oid, &block)
|
|
35
|
+
else
|
|
36
|
+
@store.register_type(oid, oid_type)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def alias_type(oid, target)
|
|
41
|
+
oid = assert_valid_registration(oid, target)
|
|
42
|
+
@store.alias_type(oid, target)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def register_with_subtype(oid, target_oid)
|
|
46
|
+
return unless @store.key?(target_oid)
|
|
47
|
+
|
|
48
|
+
register(oid) do |_, *args|
|
|
49
|
+
yield @store.lookup(target_oid, *args)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def assert_valid_registration(oid, oid_type)
|
|
54
|
+
raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil?
|
|
55
|
+
|
|
56
|
+
oid.to_i
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'oid/date_time'
|
|
4
|
+
require_relative 'oid/decimal'
|
|
5
|
+
require_relative 'oid/json'
|
|
6
|
+
require_relative 'oid/jsonb'
|
|
7
|
+
|
|
8
|
+
require_relative 'oid/type_map_initializer'
|
|
9
|
+
|
|
10
|
+
module ActiveRecord
|
|
11
|
+
module ConnectionAdapters
|
|
12
|
+
module Redshift
|
|
13
|
+
module OID # :nodoc:
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|