activerecord4-redshift-adapter 0.1.1 → 0.2.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/array_parser.rb +35 -39
- data/lib/active_record/connection_adapters/redshift/column.rb +10 -0
- data/lib/active_record/connection_adapters/redshift/database_statements.rb +37 -47
- data/lib/active_record/connection_adapters/redshift/oid.rb +14 -359
- data/lib/active_record/connection_adapters/redshift/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/redshift/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/redshift/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/redshift/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/redshift/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/redshift/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/redshift/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/redshift/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/redshift/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/redshift/oid/type_map_initializer.rb +63 -0
- data/lib/active_record/connection_adapters/redshift/quoting.rb +45 -119
- data/lib/active_record/connection_adapters/redshift/referential_integrity.rb +4 -19
- data/lib/active_record/connection_adapters/redshift/schema_definitions.rb +73 -0
- data/lib/active_record/connection_adapters/redshift/schema_statements.rb +141 -76
- data/lib/active_record/connection_adapters/redshift/utils.rb +77 -0
- data/lib/active_record/connection_adapters/redshift_adapter.rb +252 -496
- metadata +17 -11
- data/lib/active_record/connection_adapters/redshift/cast.rb +0 -156
@@ -0,0 +1,36 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Redshift
|
4
|
+
module OID # :nodoc:
|
5
|
+
class DateTime < Type::DateTime # :nodoc:
|
6
|
+
include Infinity
|
7
|
+
|
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,13 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Redshift
|
4
|
+
module OID # :nodoc:
|
5
|
+
class Decimal < Type::Decimal # :nodoc:
|
6
|
+
def infinity(options = {})
|
7
|
+
BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Redshift
|
4
|
+
module OID # :nodoc:
|
5
|
+
class Float < Type::Float # :nodoc:
|
6
|
+
include Infinity
|
7
|
+
|
8
|
+
def cast_value(value)
|
9
|
+
case value
|
10
|
+
when ::Float then value
|
11
|
+
when 'Infinity' then ::Float::INFINITY
|
12
|
+
when '-Infinity' then -::Float::INFINITY
|
13
|
+
when 'NaN' then ::Float::NAN
|
14
|
+
else value.to_f
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Redshift
|
4
|
+
module OID # :nodoc:
|
5
|
+
class Json < Type::Value # :nodoc:
|
6
|
+
include Type::Mutable
|
7
|
+
|
8
|
+
def type
|
9
|
+
:json
|
10
|
+
end
|
11
|
+
|
12
|
+
def type_cast_from_database(value)
|
13
|
+
if value.is_a?(::String)
|
14
|
+
::ActiveSupport::JSON.decode(value) rescue nil
|
15
|
+
else
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def type_cast_for_database(value)
|
21
|
+
if value.is_a?(::Array) || value.is_a?(::Hash)
|
22
|
+
::ActiveSupport::JSON.encode(value)
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def accessor
|
29
|
+
ActiveRecord::Store::StringKeyedHashAccessor
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Redshift
|
4
|
+
module OID # :nodoc:
|
5
|
+
class Jsonb < Json # :nodoc:
|
6
|
+
def type
|
7
|
+
:jsonb
|
8
|
+
end
|
9
|
+
|
10
|
+
def changed_in_place?(raw_old_value, new_value)
|
11
|
+
# Postgres does not preserve insignificant whitespaces when
|
12
|
+
# roundtripping jsonb columns. This causes some false positives for
|
13
|
+
# the comparison here. Therefore, we need to parse and re-dump the
|
14
|
+
# raw value here to ensure the insignificant whitespaces are
|
15
|
+
# consistent with our encoder's output.
|
16
|
+
raw_old_value = type_cast_for_database(type_cast_from_database(raw_old_value))
|
17
|
+
super(raw_old_value, new_value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Redshift
|
4
|
+
module OID # :nodoc:
|
5
|
+
# This class uses the data from PostgreSQL pg_type table to build
|
6
|
+
# the OID -> Type mapping.
|
7
|
+
# - OID is an integer representing the type.
|
8
|
+
# - Type is an OID::Type object.
|
9
|
+
# This class has side effects on the +store+ passed during initialization.
|
10
|
+
class TypeMapInitializer # :nodoc:
|
11
|
+
def initialize(store)
|
12
|
+
@store = store
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(records)
|
16
|
+
nodes = records.reject { |row| @store.key? row['oid'].to_i }
|
17
|
+
mapped, nodes = nodes.partition { |row| @store.key? row['typname'] }
|
18
|
+
ranges, nodes = nodes.partition { |row| row['typtype'] == 'r'.freeze }
|
19
|
+
enums, nodes = nodes.partition { |row| row['typtype'] == 'e'.freeze }
|
20
|
+
domains, nodes = nodes.partition { |row| row['typtype'] == 'd'.freeze }
|
21
|
+
arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in'.freeze }
|
22
|
+
composites, nodes = nodes.partition { |row| row['typelem'].to_i != 0 }
|
23
|
+
|
24
|
+
mapped.each { |row| register_mapped_type(row) }
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def register_mapped_type(row)
|
30
|
+
alias_type row['oid'], row['typname']
|
31
|
+
end
|
32
|
+
|
33
|
+
def register(oid, oid_type = nil, &block)
|
34
|
+
oid = assert_valid_registration(oid, oid_type || block)
|
35
|
+
if block_given?
|
36
|
+
@store.register_type(oid, &block)
|
37
|
+
else
|
38
|
+
@store.register_type(oid, oid_type)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def alias_type(oid, target)
|
43
|
+
oid = assert_valid_registration(oid, target)
|
44
|
+
@store.alias_type(oid, target)
|
45
|
+
end
|
46
|
+
|
47
|
+
def register_with_subtype(oid, target_oid)
|
48
|
+
if @store.key?(target_oid)
|
49
|
+
register(oid) do |_, *args|
|
50
|
+
yield @store.lookup(target_oid, *args)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def assert_valid_registration(oid, oid_type)
|
56
|
+
raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil?
|
57
|
+
oid.to_i
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,123 +1,17 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module ConnectionAdapters
|
3
|
-
|
3
|
+
module Redshift
|
4
4
|
module Quoting
|
5
5
|
# Escapes binary strings for bytea input to the database.
|
6
6
|
def escape_bytea(value)
|
7
|
-
|
7
|
+
@connection.escape_bytea(value) if value
|
8
8
|
end
|
9
9
|
|
10
10
|
# Unescapes bytea output from a database to the binary string it represents.
|
11
11
|
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
|
12
12
|
# on escaped binary output from database drive.
|
13
13
|
def unescape_bytea(value)
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
# Quotes PostgreSQL-specific data types for SQL input.
|
18
|
-
def quote(value, column = nil) #:nodoc:
|
19
|
-
return super unless column
|
20
|
-
|
21
|
-
sql_type = type_to_sql(column.type, column.limit, column.precision, column.scale)
|
22
|
-
|
23
|
-
case value
|
24
|
-
when Range
|
25
|
-
if /range$/ =~ sql_type
|
26
|
-
escaped = quote_string(RedshiftColumn.range_to_string(value))
|
27
|
-
"'#{escaped}'::#{sql_type}"
|
28
|
-
else
|
29
|
-
super
|
30
|
-
end
|
31
|
-
when Array
|
32
|
-
case sql_type
|
33
|
-
when 'point' then super(RedshiftColumn.point_to_string(value))
|
34
|
-
else
|
35
|
-
if column.array
|
36
|
-
"'#{RedshiftColumn.array_to_string(value, column, self).gsub(/'/, "''")}'"
|
37
|
-
else
|
38
|
-
super
|
39
|
-
end
|
40
|
-
end
|
41
|
-
when Hash
|
42
|
-
case sql_type
|
43
|
-
when 'hstore' then super(RedshiftColumn.hstore_to_string(value), column)
|
44
|
-
when 'json' then super(RedshiftColumn.json_to_string(value), column)
|
45
|
-
else super
|
46
|
-
end
|
47
|
-
when IPAddr
|
48
|
-
case sql_type
|
49
|
-
when 'inet', 'cidr' then super(RedshiftColumn.cidr_to_string(value), column)
|
50
|
-
else super
|
51
|
-
end
|
52
|
-
when Float
|
53
|
-
if value.infinite? && column.type == :datetime
|
54
|
-
"'#{value.to_s.downcase}'"
|
55
|
-
elsif value.infinite? || value.nan?
|
56
|
-
"'#{value.to_s}'"
|
57
|
-
else
|
58
|
-
super
|
59
|
-
end
|
60
|
-
when Numeric
|
61
|
-
if sql_type == 'money' || [:string, :text].include?(column.type)
|
62
|
-
# Not truly string input, so doesn't require (or allow) escape string syntax.
|
63
|
-
"'#{value}'"
|
64
|
-
else
|
65
|
-
super
|
66
|
-
end
|
67
|
-
when String
|
68
|
-
case sql_type
|
69
|
-
when 'bytea' then "'#{escape_bytea(value)}'"
|
70
|
-
when 'xml' then "xml '#{quote_string(value)}'"
|
71
|
-
when /^bit/
|
72
|
-
case value
|
73
|
-
when /\A[01]*\Z/ then "B'#{value}'" # Bit-string notation
|
74
|
-
when /\A[0-9A-F]*\Z/i then "X'#{value}'" # Hexadecimal notation
|
75
|
-
end
|
76
|
-
else
|
77
|
-
super
|
78
|
-
end
|
79
|
-
else
|
80
|
-
super
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def type_cast(value, column, array_member = false)
|
85
|
-
return super(value, column) unless column
|
86
|
-
|
87
|
-
case value
|
88
|
-
when Range
|
89
|
-
return super(value, column) unless /range$/ =~ column.sql_type
|
90
|
-
RedshiftColumn.range_to_string(value)
|
91
|
-
when NilClass
|
92
|
-
if column.array && array_member
|
93
|
-
'NULL'
|
94
|
-
elsif column.array
|
95
|
-
value
|
96
|
-
else
|
97
|
-
super(value, column)
|
98
|
-
end
|
99
|
-
when Array
|
100
|
-
case column.sql_type
|
101
|
-
when 'point' then RedshiftColumn.point_to_string(value)
|
102
|
-
else
|
103
|
-
return super(value, column) unless column.array
|
104
|
-
RedshiftColumn.array_to_string(value, column, self)
|
105
|
-
end
|
106
|
-
when String
|
107
|
-
return super(value, column) unless 'bytea' == column.sql_type
|
108
|
-
{ :value => value, :format => 1 }
|
109
|
-
when Hash
|
110
|
-
case column.sql_type
|
111
|
-
when 'hstore' then RedshiftColumn.hstore_to_string(value)
|
112
|
-
when 'json' then RedshiftColumn.json_to_string(value)
|
113
|
-
else super(value, column)
|
114
|
-
end
|
115
|
-
when IPAddr
|
116
|
-
return super(value, column) unless ['inet','cidr'].include? column.sql_type
|
117
|
-
RedshiftColumn.cidr_to_string(value)
|
118
|
-
else
|
119
|
-
super(value, column)
|
120
|
-
end
|
14
|
+
@connection.unescape_bytea(value) if value
|
121
15
|
end
|
122
16
|
|
123
17
|
# Quotes strings for use in SQL input.
|
@@ -134,14 +28,7 @@ module ActiveRecord
|
|
134
28
|
# - "schema.name".table_name
|
135
29
|
# - "schema.name"."table.name"
|
136
30
|
def quote_table_name(name)
|
137
|
-
|
138
|
-
|
139
|
-
unless name_part
|
140
|
-
quote_column_name(schema)
|
141
|
-
else
|
142
|
-
table_name, name_part = extract_pg_identifier_from_name(name_part)
|
143
|
-
"#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
|
144
|
-
end
|
31
|
+
Utils.extract_schema_qualified_name(name.to_s).quoted
|
145
32
|
end
|
146
33
|
|
147
34
|
def quote_table_name_for_assignment(table, attr)
|
@@ -161,11 +48,50 @@ module ActiveRecord
|
|
161
48
|
result = "#{result}.#{sprintf("%06d", value.usec)}"
|
162
49
|
end
|
163
50
|
|
164
|
-
if value.year
|
165
|
-
|
51
|
+
if value.year <= 0
|
52
|
+
bce_year = format("%04d", -value.year + 1)
|
53
|
+
result = result.sub(/^-?\d+/, bce_year) + " BC"
|
166
54
|
end
|
167
55
|
result
|
168
56
|
end
|
57
|
+
|
58
|
+
# Does not quote function default values for UUID columns
|
59
|
+
def quote_default_value(value, column) #:nodoc:
|
60
|
+
if column.type == :uuid && value =~ /\(\)/
|
61
|
+
value
|
62
|
+
else
|
63
|
+
quote(value, column)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def _quote(value)
|
70
|
+
case value
|
71
|
+
when Type::Binary::Data
|
72
|
+
"'#{escape_bytea(value.to_s)}'"
|
73
|
+
when Float
|
74
|
+
if value.infinite? || value.nan?
|
75
|
+
"'#{value}'"
|
76
|
+
else
|
77
|
+
super
|
78
|
+
end
|
79
|
+
else
|
80
|
+
super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def _type_cast(value)
|
85
|
+
case value
|
86
|
+
when Type::Binary::Data
|
87
|
+
# Return a bind param hash with format as binary.
|
88
|
+
# See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc
|
89
|
+
# for more information
|
90
|
+
{ value: value.to_s, format: 1 }
|
91
|
+
else
|
92
|
+
super
|
93
|
+
end
|
94
|
+
end
|
169
95
|
end
|
170
96
|
end
|
171
97
|
end
|