activerecord7-redshift-adapter-pennylane 1.0.1 → 1.0.2
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.rb +17 -0
- data/lib/active_record/connection_adapters/redshift_7_0_adapter.rb +770 -0
- data/lib/active_record/connection_adapters/redshift_7_1/array_parser.rb +92 -0
- data/lib/active_record/connection_adapters/redshift_7_1/column.rb +17 -0
- data/lib/active_record/connection_adapters/redshift_7_1/database_statements.rb +232 -0
- data/lib/active_record/connection_adapters/redshift_7_1/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/redshift_7_1/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/redshift_7_1/oid/json.rb +41 -0
- data/lib/active_record/connection_adapters/redshift_7_1/oid/jsonb.rb +25 -0
- data/lib/active_record/connection_adapters/redshift_7_1/oid/type_map_initializer.rb +62 -0
- data/lib/active_record/connection_adapters/redshift_7_1/oid.rb +17 -0
- data/lib/active_record/connection_adapters/redshift_7_1/quoting.rb +99 -0
- data/lib/active_record/connection_adapters/redshift_7_1/referential_integrity.rb +17 -0
- data/lib/active_record/connection_adapters/redshift_7_1/schema_definitions.rb +70 -0
- data/lib/active_record/connection_adapters/redshift_7_1/schema_dumper.rb +17 -0
- data/lib/active_record/connection_adapters/redshift_7_1/schema_statements.rb +424 -0
- data/lib/active_record/connection_adapters/redshift_7_1/type_metadata.rb +39 -0
- data/lib/active_record/connection_adapters/redshift_7_1/utils.rb +81 -0
- data/lib/active_record/connection_adapters/redshift_7_1_adapter.rb +769 -0
- data/lib/active_record/connection_adapters/redshift_adapter.rb +7 -768
- metadata +38 -19
- data/lib/active_record/connection_adapters/redshift/oid.rb +0 -17
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/array_parser.rb +0 -0
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/column.rb +0 -0
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/database_statements.rb +0 -0
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/oid/date_time.rb +0 -0
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/oid/decimal.rb +0 -0
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/oid/json.rb +0 -0
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/oid/jsonb.rb +0 -0
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/oid/type_map_initializer.rb +0 -0
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/quoting.rb +0 -0
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/referential_integrity.rb +0 -0
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/schema_definitions.rb +0 -0
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/schema_dumper.rb +0 -0
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/schema_statements.rb +0 -0
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/type_metadata.rb +0 -0
- /data/lib/active_record/connection_adapters/{redshift → redshift_7_0}/utils.rb +0 -0
@@ -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,232 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module Redshift
|
6
|
+
module DatabaseStatements
|
7
|
+
def explain(arel, binds = [])
|
8
|
+
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
9
|
+
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
|
10
|
+
end
|
11
|
+
|
12
|
+
class ExplainPrettyPrinter # :nodoc:
|
13
|
+
# Pretty prints the result of a EXPLAIN in a way that resembles the output of the
|
14
|
+
# PostgreSQL shell:
|
15
|
+
#
|
16
|
+
# QUERY PLAN
|
17
|
+
# ------------------------------------------------------------------------------
|
18
|
+
# Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
|
19
|
+
# Join Filter: (posts.user_id = users.id)
|
20
|
+
# -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
|
21
|
+
# Index Cond: (id = 1)
|
22
|
+
# -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
|
23
|
+
# Filter: (posts.user_id = 1)
|
24
|
+
# (6 rows)
|
25
|
+
#
|
26
|
+
def pp(result)
|
27
|
+
header = result.columns.first
|
28
|
+
lines = result.rows.map(&:first)
|
29
|
+
|
30
|
+
# We add 2 because there's one char of padding at both sides, note
|
31
|
+
# the extra hyphens in the example above.
|
32
|
+
width = [header, *lines].map(&:length).max + 2
|
33
|
+
|
34
|
+
pp = []
|
35
|
+
|
36
|
+
pp << header.center(width).rstrip
|
37
|
+
pp << '-' * width
|
38
|
+
|
39
|
+
pp += lines.map { |line| " #{line}" }
|
40
|
+
|
41
|
+
nrows = result.rows.length
|
42
|
+
rows_label = nrows == 1 ? 'row' : 'rows'
|
43
|
+
pp << "(#{nrows} #{rows_label})"
|
44
|
+
|
45
|
+
"#{pp.join("\n")}\n"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def select_value(arel, name = nil, binds = [])
|
50
|
+
# In Rails 5.2, arel_from_relation replaced binds_from_relation,
|
51
|
+
# so we see which method exists to get the variables
|
52
|
+
#
|
53
|
+
# In Rails 6.0 to_sql_and_binds began only returning sql, with
|
54
|
+
# to_sql_and_binds serving as a replacement
|
55
|
+
if respond_to?(:arel_from_relation, true)
|
56
|
+
arel = arel_from_relation(arel)
|
57
|
+
sql, binds = to_sql_and_binds(arel, binds)
|
58
|
+
else
|
59
|
+
arel, binds = binds_from_relation arel, binds
|
60
|
+
sql = to_sql(arel, binds)
|
61
|
+
end
|
62
|
+
execute_and_clear(sql, name, binds) do |result|
|
63
|
+
result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def select_values(arel, name = nil)
|
68
|
+
# In Rails 5.2, arel_from_relation replaced binds_from_relation,
|
69
|
+
# so we see which method exists to get the variables
|
70
|
+
#
|
71
|
+
# In Rails 6.0 to_sql_and_binds began only returning sql, with
|
72
|
+
# to_sql_and_binds serving as a replacement
|
73
|
+
if respond_to?(:arel_from_relation, true)
|
74
|
+
arel = arel_from_relation(arel)
|
75
|
+
sql, binds = to_sql_and_binds(arel, [])
|
76
|
+
else
|
77
|
+
arel, binds = binds_from_relation arel, []
|
78
|
+
sql = to_sql(arel, binds)
|
79
|
+
end
|
80
|
+
|
81
|
+
execute_and_clear(sql, name, binds) do |result|
|
82
|
+
if result.nfields > 0
|
83
|
+
result.column_values(0)
|
84
|
+
else
|
85
|
+
[]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Executes a SELECT query and returns an array of rows. Each row is an
|
91
|
+
# array of field values.
|
92
|
+
def select_rows(arel, name = nil, binds = [])
|
93
|
+
if respond_to?(:arel_from_relation, true)
|
94
|
+
arel = arel_from_relation(arel)
|
95
|
+
sql, binds = to_sql_and_binds(arel, [])
|
96
|
+
else
|
97
|
+
arel, binds = binds_from_relation arel, []
|
98
|
+
sql = to_sql(arel, binds)
|
99
|
+
end
|
100
|
+
execute_and_clear(sql, name, binds, &:values)
|
101
|
+
end
|
102
|
+
|
103
|
+
# The internal PostgreSQL identifier of the money data type.
|
104
|
+
MONEY_COLUMN_TYPE_OID = 790 # :nodoc:
|
105
|
+
# The internal PostgreSQL identifier of the BYTEA data type.
|
106
|
+
BYTEA_COLUMN_TYPE_OID = 17 # :nodoc:
|
107
|
+
|
108
|
+
# create a 2D array representing the result set
|
109
|
+
def result_as_array(res) # :nodoc:
|
110
|
+
# check if we have any binary column and if they need escaping
|
111
|
+
ftypes = Array.new(res.nfields) do |i|
|
112
|
+
[i, res.ftype(i)]
|
113
|
+
end
|
114
|
+
|
115
|
+
rows = res.values
|
116
|
+
return rows unless ftypes.any? do |_, x|
|
117
|
+
[BYTEA_COLUMN_TYPE_OID, MONEY_COLUMN_TYPE_OID].include?(x)
|
118
|
+
end
|
119
|
+
|
120
|
+
typehash = ftypes.group_by { |_, type| type }
|
121
|
+
binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
|
122
|
+
monies = typehash[MONEY_COLUMN_TYPE_OID] || []
|
123
|
+
|
124
|
+
rows.each do |row|
|
125
|
+
# unescape string passed BYTEA field (OID == 17)
|
126
|
+
binaries.each do |index, _|
|
127
|
+
row[index] = unescape_bytea(row[index])
|
128
|
+
end
|
129
|
+
|
130
|
+
# If this is a money type column and there are any currency symbols,
|
131
|
+
# then strip them off. Indeed it would be prettier to do this in
|
132
|
+
# PostgreSQLColumn.string_to_decimal but would break form input
|
133
|
+
# fields that call value_before_type_cast.
|
134
|
+
monies.each do |index, _|
|
135
|
+
data = row[index]
|
136
|
+
# Because money output is formatted according to the locale, there are two
|
137
|
+
# cases to consider (note the decimal separators):
|
138
|
+
# (1) $12,345,678.12
|
139
|
+
# (2) $12.345.678,12
|
140
|
+
case data
|
141
|
+
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
|
142
|
+
data.gsub!(/[^-\d.]/, '')
|
143
|
+
when /^-?\D+[\d.]+,\d{2}$/ # (2)
|
144
|
+
data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Queries the database and returns the results in an Array-like object
|
151
|
+
def query(sql, name = nil) # :nodoc:
|
152
|
+
log(sql, name) do
|
153
|
+
result_as_array @raw_connection.async_exec(sql)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Executes an SQL statement, returning a PG::Result object on success
|
158
|
+
# or raising a PG::Error exception otherwise.
|
159
|
+
def execute(sql, name = nil)
|
160
|
+
log(sql, name) do
|
161
|
+
@raw_connection.async_exec(sql)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def exec_query(sql, name = 'SQL', binds = [], prepare: false)
|
166
|
+
execute_and_clear(sql, name, binds, prepare: prepare) do |result|
|
167
|
+
types = {}
|
168
|
+
fields = result.fields
|
169
|
+
fields.each_with_index do |fname, i|
|
170
|
+
ftype = result.ftype i
|
171
|
+
fmod = result.fmod i
|
172
|
+
types[fname] = get_oid_type(ftype, fmod, fname)
|
173
|
+
end
|
174
|
+
ActiveRecord::Result.new(fields, result.values, types)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
alias internal_exec_query exec_query
|
178
|
+
|
179
|
+
def exec_delete(sql, name = nil, binds = [])
|
180
|
+
execute_and_clear(sql, name, binds, &:cmd_tuples)
|
181
|
+
end
|
182
|
+
alias exec_update exec_delete
|
183
|
+
|
184
|
+
def sql_for_insert(sql, pk, binds, returning)
|
185
|
+
if pk.nil?
|
186
|
+
# Extract the table from the insert sql. Yuck.
|
187
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
188
|
+
pk = primary_key(table_ref) if table_ref
|
189
|
+
end
|
190
|
+
|
191
|
+
sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk && use_insert_returning?
|
192
|
+
|
193
|
+
super
|
194
|
+
end
|
195
|
+
|
196
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
|
197
|
+
val = exec_query(sql, name, binds)
|
198
|
+
if !use_insert_returning? && pk
|
199
|
+
unless sequence_name
|
200
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
201
|
+
sequence_name = default_sequence_name(table_ref, pk)
|
202
|
+
return val unless sequence_name
|
203
|
+
end
|
204
|
+
last_insert_id_result(sequence_name)
|
205
|
+
else
|
206
|
+
val
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Begins a transaction.
|
211
|
+
def begin_db_transaction
|
212
|
+
execute 'BEGIN'
|
213
|
+
end
|
214
|
+
|
215
|
+
def begin_isolated_db_transaction(isolation)
|
216
|
+
begin_db_transaction
|
217
|
+
execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
|
218
|
+
end
|
219
|
+
|
220
|
+
# Commits a transaction.
|
221
|
+
def commit_db_transaction
|
222
|
+
execute 'COMMIT'
|
223
|
+
end
|
224
|
+
|
225
|
+
# Aborts a transaction.
|
226
|
+
def exec_rollback_db_transaction
|
227
|
+
execute 'ROLLBACK'
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
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
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module Redshift
|
6
|
+
module Quoting
|
7
|
+
# Escapes binary strings for bytea input to the database.
|
8
|
+
def escape_bytea(value)
|
9
|
+
@raw_connection.escape_bytea(value) if value
|
10
|
+
end
|
11
|
+
|
12
|
+
# Unescapes bytea output from a database to the binary string it represents.
|
13
|
+
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
|
14
|
+
# on escaped binary output from database drive.
|
15
|
+
def unescape_bytea(value)
|
16
|
+
@raw_connection.unescape_bytea(value) if value
|
17
|
+
end
|
18
|
+
|
19
|
+
# Quotes strings for use in SQL input.
|
20
|
+
def quote_string(s) # :nodoc:
|
21
|
+
@raw_connection.escape(s)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Checks the following cases:
|
25
|
+
#
|
26
|
+
# - table_name
|
27
|
+
# - "table.name"
|
28
|
+
# - schema_name.table_name
|
29
|
+
# - schema_name."table.name"
|
30
|
+
# - "schema.name".table_name
|
31
|
+
# - "schema.name"."table.name"
|
32
|
+
def quote_table_name(name)
|
33
|
+
Utils.extract_schema_qualified_name(name.to_s).quoted
|
34
|
+
end
|
35
|
+
|
36
|
+
def quote_table_name_for_assignment(_table, attr)
|
37
|
+
quote_column_name(attr)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Quotes column names for use in SQL queries.
|
41
|
+
def quote_column_name(name) # :nodoc:
|
42
|
+
PG::Connection.quote_ident(name.to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Quotes schema names for use in SQL queries.
|
46
|
+
def quote_schema_name(name)
|
47
|
+
PG::Connection.quote_ident(name)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Quote date/time values for use in SQL input.
|
51
|
+
def quoted_date(value) # :nodoc:
|
52
|
+
result = super
|
53
|
+
|
54
|
+
if value.year <= 0
|
55
|
+
bce_year = format('%04d', -value.year + 1)
|
56
|
+
result = "#{result.sub(/^-?\d+/, bce_year)} BC"
|
57
|
+
end
|
58
|
+
result
|
59
|
+
end
|
60
|
+
|
61
|
+
# Does not quote function default values for UUID columns
|
62
|
+
def quote_default_value(value, column) # :nodoc:
|
63
|
+
if column.type == :uuid && value =~ /\(\)/
|
64
|
+
value
|
65
|
+
else
|
66
|
+
quote(value, column)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def quote(value)
|
71
|
+
case value
|
72
|
+
when Type::Binary::Data
|
73
|
+
"'#{escape_bytea(value.to_s)}'"
|
74
|
+
when Float
|
75
|
+
if value.infinite? || value.nan?
|
76
|
+
"'#{value}'"
|
77
|
+
else
|
78
|
+
super
|
79
|
+
end
|
80
|
+
else
|
81
|
+
super
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def type_cast(value)
|
86
|
+
case value
|
87
|
+
when Type::Binary::Data
|
88
|
+
# Return a bind param hash with format as binary.
|
89
|
+
# See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc
|
90
|
+
# for more information
|
91
|
+
{ value: value.to_s, format: 1 }
|
92
|
+
else
|
93
|
+
super
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module Redshift
|
6
|
+
module ReferentialIntegrity # :nodoc:
|
7
|
+
def supports_disable_referential_integrity? # :nodoc:
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def disable_referential_integrity # :nodoc:
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|