activerecord7-redshift-adapter-pennylane 1.0.3 → 1.0.5
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/oid/decimal.rb +1 -1
- data/lib/active_record/connection_adapters/redshift_7_0/schema_statements.rb +3 -3
- data/lib/active_record/connection_adapters/redshift_7_0_adapter.rb +0 -2
- data/lib/active_record/connection_adapters/redshift_7_1/oid/decimal.rb +1 -1
- data/lib/active_record/connection_adapters/redshift_7_1/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/redshift_7_1/schema_statements.rb +3 -3
- data/lib/active_record/connection_adapters/redshift_7_1_adapter.rb +0 -1
- data/lib/active_record/connection_adapters/redshift_7_2/array_parser.rb +92 -0
- data/lib/active_record/connection_adapters/redshift_7_2/column.rb +17 -0
- data/lib/active_record/connection_adapters/redshift_7_2/database_statements.rb +180 -0
- data/lib/active_record/connection_adapters/redshift_7_2/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/redshift_7_2/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/redshift_7_2/oid/json.rb +41 -0
- data/lib/active_record/connection_adapters/redshift_7_2/oid/jsonb.rb +25 -0
- data/lib/active_record/connection_adapters/redshift_7_2/oid/type_map_initializer.rb +62 -0
- data/lib/active_record/connection_adapters/redshift_7_2/oid.rb +17 -0
- data/lib/active_record/connection_adapters/redshift_7_2/quoting.rb +164 -0
- data/lib/active_record/connection_adapters/redshift_7_2/referential_integrity.rb +17 -0
- data/lib/active_record/connection_adapters/redshift_7_2/schema_definitions.rb +70 -0
- data/lib/active_record/connection_adapters/redshift_7_2/schema_dumper.rb +17 -0
- data/lib/active_record/connection_adapters/redshift_7_2/schema_statements.rb +422 -0
- data/lib/active_record/connection_adapters/redshift_7_2/type_metadata.rb +43 -0
- data/lib/active_record/connection_adapters/redshift_7_2/utils.rb +81 -0
- data/lib/active_record/connection_adapters/redshift_7_2_adapter.rb +847 -0
- data/lib/active_record/connection_adapters/redshift_adapter.rb +8 -2
- data/lib/active_record/tasks/redshift_7_0_tasks.rb +148 -0
- data/lib/active_record/tasks/redshift_7_1_tasks.rb +151 -0
- data/lib/active_record/tasks/redshift_7_2_tasks.rb +151 -0
- data/lib/active_record/tasks/redshift_tasks.rb +11 -0
- data/lib/activerecord7-redshift-adapter-pennylane.rb +13 -0
- metadata +25 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 26e7edd35acf5f404cb34a387350b1f0a10460b70d6cdffa78e146ff2e5e9bae
|
4
|
+
data.tar.gz: c6b7aa54b4e1d7487e40f048b035e90189e6ac3aed91ffddbcd706297b12de27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f2a95375595b29b82be85c059d2ae82cfd67269ddb603531e700fe45e35ce3750fd10f913afff46138284673a652e8af01a32b223f7de1ad830287820bcda4bc
|
7
|
+
data.tar.gz: 96a9934cc31208484b910adbe2cb22db6872ab16dbb2b011ce1d88a80785fffcf1bdecfeb6a0b065b34cea7a1b38241c1eca425606d2863092af4d2d9189bab6
|
@@ -24,7 +24,7 @@ module ActiveRecord
|
|
24
24
|
module SchemaStatements
|
25
25
|
# Drops the database specified on the +name+ attribute
|
26
26
|
# and creates it again using the provided +options+.
|
27
|
-
def recreate_database(name,
|
27
|
+
def recreate_database(name, options = {}) # :nodoc:
|
28
28
|
drop_database(name)
|
29
29
|
create_database(name, options)
|
30
30
|
end
|
@@ -37,7 +37,7 @@ module ActiveRecord
|
|
37
37
|
# Example:
|
38
38
|
# create_database config[:database], config
|
39
39
|
# create_database 'foo_development', encoding: 'unicode'
|
40
|
-
def create_database(name,
|
40
|
+
def create_database(name, options = {})
|
41
41
|
options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
|
42
42
|
|
43
43
|
option_string = options.inject('') do |memo, (key, value)|
|
@@ -129,7 +129,7 @@ module ActiveRecord
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def drop_table(table_name, **options)
|
132
|
-
execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
132
|
+
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
133
133
|
end
|
134
134
|
|
135
135
|
# Returns true if schema exists.
|
@@ -20,8 +20,6 @@ require 'pg'
|
|
20
20
|
|
21
21
|
require 'ipaddr'
|
22
22
|
|
23
|
-
ActiveRecord::Tasks::DatabaseTasks.register_task(/redshift/, 'ActiveRecord::Tasks::PostgreSQLDatabaseTasks')
|
24
|
-
|
25
23
|
module ActiveRecord
|
26
24
|
module ConnectionHandling # :nodoc:
|
27
25
|
RS_VALID_CONN_PARAMS = %i[host hostaddr port dbname user password connect_timeout
|
@@ -44,7 +44,7 @@ module ActiveRecord
|
|
44
44
|
|
45
45
|
# Quotes column names for use in SQL queries.
|
46
46
|
def quote_column_name(name) # :nodoc:
|
47
|
-
QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(
|
47
|
+
QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(name.to_s).freeze
|
48
48
|
end
|
49
49
|
|
50
50
|
# Quotes schema names for use in SQL queries.
|
@@ -24,7 +24,7 @@ module ActiveRecord
|
|
24
24
|
module SchemaStatements
|
25
25
|
# Drops the database specified on the +name+ attribute
|
26
26
|
# and creates it again using the provided +options+.
|
27
|
-
def recreate_database(name,
|
27
|
+
def recreate_database(name, options = {}) # :nodoc:
|
28
28
|
drop_database(name)
|
29
29
|
create_database(name, options)
|
30
30
|
end
|
@@ -37,7 +37,7 @@ module ActiveRecord
|
|
37
37
|
# Example:
|
38
38
|
# create_database config[:database], config
|
39
39
|
# create_database 'foo_development', encoding: 'unicode'
|
40
|
-
def create_database(name,
|
40
|
+
def create_database(name, options = {})
|
41
41
|
options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
|
42
42
|
|
43
43
|
option_string = options.inject('') do |memo, (key, value)|
|
@@ -129,7 +129,7 @@ module ActiveRecord
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def drop_table(table_name, **options)
|
132
|
-
execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
132
|
+
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
133
133
|
end
|
134
134
|
|
135
135
|
# Returns true if schema exists.
|
@@ -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,17 @@
|
|
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
|
+
def initialize(name, default, sql_type_metadata, null = true, default_function = nil, **)
|
13
|
+
super name, default, sql_type_metadata, null, default_function
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,180 @@
|
|
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, async: false, allow_retry: false, materialize_transactions: true)
|
53
|
+
log(sql, name, async: async) do
|
54
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
55
|
+
result = conn.async_exec(sql)
|
56
|
+
verified!
|
57
|
+
handle_warnings(result)
|
58
|
+
result
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
|
64
|
+
execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
|
65
|
+
types = {}
|
66
|
+
fields = result.fields
|
67
|
+
fields.each_with_index do |fname, i|
|
68
|
+
ftype = result.ftype i
|
69
|
+
fmod = result.fmod i
|
70
|
+
types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
|
71
|
+
end
|
72
|
+
build_result(columns: fields, rows: result.values, column_types: types)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
77
|
+
execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
|
78
|
+
end
|
79
|
+
alias :exec_update :exec_delete
|
80
|
+
|
81
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
|
82
|
+
if use_insert_returning? || pk == false
|
83
|
+
super
|
84
|
+
else
|
85
|
+
result = internal_exec_query(sql, name, binds)
|
86
|
+
unless sequence_name
|
87
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
88
|
+
if table_ref
|
89
|
+
pk = primary_key(table_ref) if pk.nil?
|
90
|
+
pk = suppress_composite_primary_key(pk)
|
91
|
+
sequence_name = default_sequence_name(table_ref, pk)
|
92
|
+
end
|
93
|
+
return result unless sequence_name
|
94
|
+
end
|
95
|
+
last_insert_id_result(sequence_name)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Begins a transaction.
|
100
|
+
def begin_db_transaction
|
101
|
+
internal_execute('BEGIN', allow_retry: true, materialize_transactions: false)
|
102
|
+
end
|
103
|
+
|
104
|
+
def begin_isolated_db_transaction(isolation)
|
105
|
+
begin_db_transaction
|
106
|
+
internal_execute("SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", allow_retry: true, materialize_transactions: false)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Commits a transaction.
|
110
|
+
def commit_db_transaction
|
111
|
+
internal_execute('COMMIT', allow_retry: false, materialize_transactions: true)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Aborts a transaction.
|
115
|
+
def exec_rollback_db_transaction
|
116
|
+
internal_execute('ROLLBACK', allow_retry: false, materialize_transactions: true)
|
117
|
+
end
|
118
|
+
|
119
|
+
def exec_restart_db_transaction # :nodoc:
|
120
|
+
cancel_any_running_query
|
121
|
+
exec_rollback_db_transaction
|
122
|
+
begin_db_transaction
|
123
|
+
end
|
124
|
+
|
125
|
+
# From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
|
126
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
|
127
|
+
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
128
|
+
|
129
|
+
def high_precision_current_timestamp
|
130
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP
|
131
|
+
end
|
132
|
+
|
133
|
+
def build_explain_clause(options = [])
|
134
|
+
return "EXPLAIN" if options.empty?
|
135
|
+
|
136
|
+
"EXPLAIN (#{options.join(", ").upcase})"
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
IDLE_TRANSACTION_STATUSES = [PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR]
|
142
|
+
private_constant :IDLE_TRANSACTION_STATUSES
|
143
|
+
|
144
|
+
def cancel_any_running_query
|
145
|
+
return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
|
146
|
+
|
147
|
+
@raw_connection.cancel
|
148
|
+
@raw_connection.block
|
149
|
+
rescue PG::Error
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns the current ID of a table's sequence.
|
153
|
+
def last_insert_id_result(sequence_name)
|
154
|
+
internal_exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
|
155
|
+
end
|
156
|
+
|
157
|
+
def returning_column_values(result)
|
158
|
+
result.rows.first
|
159
|
+
end
|
160
|
+
|
161
|
+
def suppress_composite_primary_key(pk)
|
162
|
+
pk unless pk.is_a?(Array)
|
163
|
+
end
|
164
|
+
|
165
|
+
def handle_warnings(sql)
|
166
|
+
@notice_receiver_sql_warnings.each do |warning|
|
167
|
+
next if warning_ignored?(warning)
|
168
|
+
|
169
|
+
warning.sql = sql
|
170
|
+
ActiveRecord.db_warnings_action.call(warning)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def warning_ignored?(warning)
|
175
|
+
["WARNING", "ERROR", "FATAL", "PANIC"].exclude?(warning.level) || super
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
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
|