activerecord-redshift-adapter-ng 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +0 -33
- data/README.md +12 -16
- data/lib/active_record/connection_adapters/redshift/column.rb +11 -1
- data/lib/active_record/connection_adapters/redshift/oid.rb +15 -0
- data/lib/active_record/connection_adapters/redshift/oid/array.rb +96 -0
- data/lib/active_record/connection_adapters/redshift/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/redshift/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/redshift/oid/bytea.rb +14 -0
- data/lib/active_record/connection_adapters/redshift/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/redshift/oid/date_time.rb +0 -9
- data/lib/active_record/connection_adapters/redshift/oid/enum.rb +17 -0
- data/lib/active_record/connection_adapters/redshift/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/redshift/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/redshift/oid/json.rb +1 -1
- data/lib/active_record/connection_adapters/redshift/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/redshift/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/redshift/oid/range.rb +76 -0
- data/lib/active_record/connection_adapters/redshift/oid/specialized_string.rb +15 -0
- data/lib/active_record/connection_adapters/redshift/oid/type_map_initializer.rb +42 -20
- data/lib/active_record/connection_adapters/redshift/oid/uuid.rb +28 -0
- data/lib/active_record/connection_adapters/redshift/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/redshift/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/redshift/quoting.rb +10 -0
- data/lib/active_record/connection_adapters/redshift/referential_integrity.rb +15 -0
- data/lib/active_record/connection_adapters/redshift/schema_definitions.rb +85 -0
- data/lib/active_record/connection_adapters/redshift/schema_statements.rb +171 -8
- data/lib/active_record/connection_adapters/redshift/utils.rb +4 -15
- data/lib/active_record/connection_adapters/redshift_adapter.rb +119 -38
- metadata +26 -9
@@ -0,0 +1,43 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Redshift
|
4
|
+
module OID # :nodoc:
|
5
|
+
class Money < Type::Decimal # :nodoc:
|
6
|
+
include Infinity
|
7
|
+
|
8
|
+
class_attribute :precision
|
9
|
+
|
10
|
+
def type
|
11
|
+
:money
|
12
|
+
end
|
13
|
+
|
14
|
+
def scale
|
15
|
+
2
|
16
|
+
end
|
17
|
+
|
18
|
+
def cast_value(value)
|
19
|
+
return value unless ::String === value
|
20
|
+
|
21
|
+
# Because money output is formatted according to the locale, there are two
|
22
|
+
# cases to consider (note the decimal separators):
|
23
|
+
# (1) $12,345,678.12
|
24
|
+
# (2) $12.345.678,12
|
25
|
+
# Negative values are represented as follows:
|
26
|
+
# (3) -$2.55
|
27
|
+
# (4) ($2.55)
|
28
|
+
|
29
|
+
value.sub!(/^\((.+)\)$/, '-\1') # (4)
|
30
|
+
case value
|
31
|
+
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
|
32
|
+
value.gsub!(/[^-\d.]/, '')
|
33
|
+
when /^-?\D+[\d.]+,\d{2}$/ # (2)
|
34
|
+
value.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
|
35
|
+
end
|
36
|
+
|
37
|
+
super(value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Redshift
|
4
|
+
module OID # :nodoc:
|
5
|
+
class Point < Type::Value # :nodoc:
|
6
|
+
include Type::Mutable
|
7
|
+
|
8
|
+
def type
|
9
|
+
:point
|
10
|
+
end
|
11
|
+
|
12
|
+
def type_cast(value)
|
13
|
+
case value
|
14
|
+
when ::String
|
15
|
+
if value[0] == '(' && value[-1] == ')'
|
16
|
+
value = value[1...-1]
|
17
|
+
end
|
18
|
+
type_cast(value.split(','))
|
19
|
+
when ::Array
|
20
|
+
value.map { |v| Float(v) }
|
21
|
+
else
|
22
|
+
value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def type_cast_for_database(value)
|
27
|
+
if value.is_a?(::Array)
|
28
|
+
"(#{number_for_point(value[0])},#{number_for_point(value[1])})"
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def number_for_point(number)
|
37
|
+
number.to_s.gsub(/\.0$/, '')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Redshift
|
4
|
+
module OID # :nodoc:
|
5
|
+
class Range < Type::Value # :nodoc:
|
6
|
+
attr_reader :subtype, :type
|
7
|
+
|
8
|
+
def initialize(subtype, type)
|
9
|
+
@subtype = subtype
|
10
|
+
@type = type
|
11
|
+
end
|
12
|
+
|
13
|
+
def type_cast_for_schema(value)
|
14
|
+
value.inspect.gsub('Infinity', '::Float::INFINITY')
|
15
|
+
end
|
16
|
+
|
17
|
+
def cast_value(value)
|
18
|
+
return if value == 'empty'
|
19
|
+
return value if value.is_a?(::Range)
|
20
|
+
|
21
|
+
extracted = extract_bounds(value)
|
22
|
+
from = type_cast_single extracted[:from]
|
23
|
+
to = type_cast_single extracted[:to]
|
24
|
+
|
25
|
+
if !infinity?(from) && extracted[:exclude_start]
|
26
|
+
if from.respond_to?(:succ)
|
27
|
+
from = from.succ
|
28
|
+
ActiveSupport::Deprecation.warn \
|
29
|
+
"Excluding the beginning of a Range is only partialy supported " \
|
30
|
+
"through `#succ`. This is not reliable and will be removed in " \
|
31
|
+
"the future."
|
32
|
+
else
|
33
|
+
raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
::Range.new(from, to, extracted[:exclude_end])
|
37
|
+
end
|
38
|
+
|
39
|
+
def type_cast_for_database(value)
|
40
|
+
if value.is_a?(::Range)
|
41
|
+
from = type_cast_single_for_database(value.begin)
|
42
|
+
to = type_cast_single_for_database(value.end)
|
43
|
+
"[#{from},#{to}#{value.exclude_end? ? ')' : ']'}"
|
44
|
+
else
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def type_cast_single(value)
|
52
|
+
infinity?(value) ? value : @subtype.type_cast_from_database(value)
|
53
|
+
end
|
54
|
+
|
55
|
+
def type_cast_single_for_database(value)
|
56
|
+
infinity?(value) ? '' : @subtype.type_cast_for_database(value)
|
57
|
+
end
|
58
|
+
|
59
|
+
def extract_bounds(value)
|
60
|
+
from, to = value[1..-2].split(',')
|
61
|
+
{
|
62
|
+
from: (value[1] == ',' || from == '-infinity') ? @subtype.infinity(negative: true) : from,
|
63
|
+
to: (value[-2] == ',' || to == 'infinity') ? @subtype.infinity : to,
|
64
|
+
exclude_start: (value[0] == '('),
|
65
|
+
exclude_end: (value[-1] == ')')
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def infinity?(value)
|
70
|
+
value.respond_to?(:infinite?) && value.infinite?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -4,7 +4,7 @@ module ActiveRecord
|
|
4
4
|
module OID # :nodoc:
|
5
5
|
# This class uses the data from PostgreSQL pg_type table to build
|
6
6
|
# the OID -> Type mapping.
|
7
|
-
# - OID is
|
7
|
+
# - OID is and integer representing the type.
|
8
8
|
# - Type is an OID::Type object.
|
9
9
|
# This class has side effects on the +store+ passed during initialization.
|
10
10
|
class TypeMapInitializer # :nodoc:
|
@@ -15,43 +15,65 @@ module ActiveRecord
|
|
15
15
|
def run(records)
|
16
16
|
nodes = records.reject { |row| @store.key? row['oid'].to_i }
|
17
17
|
mapped, nodes = nodes.partition { |row| @store.key? row['typname'] }
|
18
|
-
ranges, nodes = nodes.partition { |row| row['typtype'] == 'r'
|
19
|
-
enums, nodes = nodes.partition { |row| row['typtype'] == 'e'
|
20
|
-
domains, nodes = nodes.partition { |row| row['typtype'] == 'd'
|
21
|
-
arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in'
|
22
|
-
composites, nodes = nodes.partition { |row| row['typelem']
|
18
|
+
ranges, nodes = nodes.partition { |row| row['typtype'] == 'r' }
|
19
|
+
enums, nodes = nodes.partition { |row| row['typtype'] == 'e' }
|
20
|
+
domains, nodes = nodes.partition { |row| row['typtype'] == 'd' }
|
21
|
+
arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
|
22
|
+
composites, nodes = nodes.partition { |row| row['typelem'] != '0' }
|
23
23
|
|
24
24
|
mapped.each { |row| register_mapped_type(row) }
|
25
|
+
enums.each { |row| register_enum_type(row) }
|
26
|
+
domains.each { |row| register_domain_type(row) }
|
27
|
+
arrays.each { |row| register_array_type(row) }
|
28
|
+
ranges.each { |row| register_range_type(row) }
|
29
|
+
composites.each { |row| register_composite_type(row) }
|
25
30
|
end
|
26
31
|
|
27
32
|
private
|
28
|
-
|
29
33
|
def register_mapped_type(row)
|
30
34
|
alias_type row['oid'], row['typname']
|
31
35
|
end
|
32
36
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
+
def register_enum_type(row)
|
38
|
+
register row['oid'], OID::Enum.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def register_array_type(row)
|
42
|
+
if subtype = @store.lookup(row['typelem'].to_i)
|
43
|
+
register row['oid'], OID::Array.new(subtype, row['typdelim'])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def register_range_type(row)
|
48
|
+
if subtype = @store.lookup(row['rngsubtype'].to_i)
|
49
|
+
register row['oid'], OID::Range.new(subtype, row['typname'].to_sym)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def register_domain_type(row)
|
54
|
+
if base_type = @store.lookup(row["typbasetype"].to_i)
|
55
|
+
register row['oid'], base_type
|
37
56
|
else
|
38
|
-
|
57
|
+
warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}."
|
39
58
|
end
|
40
59
|
end
|
41
60
|
|
61
|
+
def register_composite_type(row)
|
62
|
+
if subtype = @store.lookup(row['typelem'].to_i)
|
63
|
+
register row['oid'], OID::Vector.new(row['typdelim'], subtype)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def register(oid, oid_type)
|
68
|
+
oid = assert_valid_registration(oid, oid_type)
|
69
|
+
@store.register_type(oid, oid_type)
|
70
|
+
end
|
71
|
+
|
42
72
|
def alias_type(oid, target)
|
43
73
|
oid = assert_valid_registration(oid, target)
|
44
74
|
@store.alias_type(oid, target)
|
45
75
|
end
|
46
76
|
|
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
77
|
def assert_valid_registration(oid, oid_type)
|
56
78
|
raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil?
|
57
79
|
oid.to_i
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Redshift
|
4
|
+
module OID # :nodoc:
|
5
|
+
class Uuid < Type::Value # :nodoc:
|
6
|
+
RFC_4122 = %r{\A\{?[a-fA-F0-9]{4}-?
|
7
|
+
[a-fA-F0-9]{4}-?
|
8
|
+
[a-fA-F0-9]{4}-?
|
9
|
+
[1-5][a-fA-F0-9]{3}-?
|
10
|
+
[8-Bab][a-fA-F0-9]{3}-?
|
11
|
+
[a-fA-F0-9]{4}-?
|
12
|
+
[a-fA-F0-9]{4}-?
|
13
|
+
[a-fA-F0-9]{4}-?\}?\z}x
|
14
|
+
|
15
|
+
alias_method :type_cast_for_database, :type_cast_from_database
|
16
|
+
|
17
|
+
def type
|
18
|
+
:uuid
|
19
|
+
end
|
20
|
+
|
21
|
+
def type_cast(value)
|
22
|
+
value.to_s[RFC_4122, 0]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Redshift
|
4
|
+
module OID # :nodoc:
|
5
|
+
class Vector < Type::Value # :nodoc:
|
6
|
+
attr_reader :delim, :subtype
|
7
|
+
|
8
|
+
# +delim+ corresponds to the `typdelim` column in the pg_types
|
9
|
+
# table. +subtype+ is derived from the `typelem` column in the
|
10
|
+
# pg_types table.
|
11
|
+
def initialize(delim, subtype)
|
12
|
+
@delim = delim
|
13
|
+
@subtype = subtype
|
14
|
+
end
|
15
|
+
|
16
|
+
# FIXME: this should probably split on +delim+ and use +subtype+
|
17
|
+
# to cast the values. Unfortunately, the current Rails behavior
|
18
|
+
# is to just return the string.
|
19
|
+
def type_cast(value)
|
20
|
+
value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Redshift
|
4
|
+
module OID # :nodoc:
|
5
|
+
class Xml < Type::String # :nodoc:
|
6
|
+
def type
|
7
|
+
:xml
|
8
|
+
end
|
9
|
+
|
10
|
+
def type_cast_for_database(value)
|
11
|
+
return unless value
|
12
|
+
Data.new(super)
|
13
|
+
end
|
14
|
+
|
15
|
+
class Data # :nodoc:
|
16
|
+
def initialize(value)
|
17
|
+
@value = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
@value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -70,6 +70,14 @@ module ActiveRecord
|
|
70
70
|
case value
|
71
71
|
when Type::Binary::Data
|
72
72
|
"'#{escape_bytea(value.to_s)}'"
|
73
|
+
when OID::Xml::Data
|
74
|
+
"xml '#{quote_string(value.to_s)}'"
|
75
|
+
when OID::Bit::Data
|
76
|
+
if value.binary?
|
77
|
+
"B'#{value}'"
|
78
|
+
elsif value.hex?
|
79
|
+
"X'#{value}'"
|
80
|
+
end
|
73
81
|
when Float
|
74
82
|
if value.infinite? || value.nan?
|
75
83
|
"'#{value}'"
|
@@ -88,6 +96,8 @@ module ActiveRecord
|
|
88
96
|
# See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc
|
89
97
|
# for more information
|
90
98
|
{ value: value.to_s, format: 1 }
|
99
|
+
when OID::Xml::Data, OID::Bit::Data
|
100
|
+
value.to_s
|
91
101
|
else
|
92
102
|
super
|
93
103
|
end
|
@@ -7,7 +7,22 @@ module ActiveRecord
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def disable_referential_integrity # :nodoc:
|
10
|
+
if supports_disable_referential_integrity?
|
11
|
+
begin
|
12
|
+
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
13
|
+
rescue
|
14
|
+
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER USER" }.join(";"))
|
15
|
+
end
|
16
|
+
end
|
10
17
|
yield
|
18
|
+
ensure
|
19
|
+
if supports_disable_referential_integrity?
|
20
|
+
begin
|
21
|
+
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
22
|
+
rescue
|
23
|
+
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER USER" }.join(";"))
|
24
|
+
end
|
25
|
+
end
|
11
26
|
end
|
12
27
|
end
|
13
28
|
end
|
@@ -2,6 +2,64 @@ module ActiveRecord
|
|
2
2
|
module ConnectionAdapters
|
3
3
|
module Redshift
|
4
4
|
module ColumnMethods
|
5
|
+
def xml(*args)
|
6
|
+
options = args.extract_options!
|
7
|
+
column(args[0], :xml, options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def tsvector(*args)
|
11
|
+
options = args.extract_options!
|
12
|
+
column(args[0], :tsvector, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def int4range(name, options = {})
|
16
|
+
column(name, :int4range, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def int8range(name, options = {})
|
20
|
+
column(name, :int8range, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def tsrange(name, options = {})
|
24
|
+
column(name, :tsrange, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def tstzrange(name, options = {})
|
28
|
+
column(name, :tstzrange, options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def numrange(name, options = {})
|
32
|
+
column(name, :numrange, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def daterange(name, options = {})
|
36
|
+
column(name, :daterange, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def hstore(name, options = {})
|
40
|
+
column(name, :hstore, options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def ltree(name, options = {})
|
44
|
+
column(name, :ltree, options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def inet(name, options = {})
|
48
|
+
column(name, :inet, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def cidr(name, options = {})
|
52
|
+
column(name, :cidr, options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def macaddr(name, options = {})
|
56
|
+
column(name, :macaddr, options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def uuid(name, options = {})
|
60
|
+
column(name, :uuid, options)
|
61
|
+
end
|
62
|
+
|
5
63
|
def json(name, options = {})
|
6
64
|
column(name, :json, options)
|
7
65
|
end
|
@@ -9,9 +67,30 @@ module ActiveRecord
|
|
9
67
|
def jsonb(name, options = {})
|
10
68
|
column(name, :jsonb, options)
|
11
69
|
end
|
70
|
+
|
71
|
+
def citext(name, options = {})
|
72
|
+
column(name, :citext, options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def point(name, options = {})
|
76
|
+
column(name, :point, options)
|
77
|
+
end
|
78
|
+
|
79
|
+
def bit(name, options)
|
80
|
+
column(name, :bit, options)
|
81
|
+
end
|
82
|
+
|
83
|
+
def bit_varying(name, options)
|
84
|
+
column(name, :bit_varying, options)
|
85
|
+
end
|
86
|
+
|
87
|
+
def money(name, options)
|
88
|
+
column(name, :money, options)
|
89
|
+
end
|
12
90
|
end
|
13
91
|
|
14
92
|
class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
|
93
|
+
attr_accessor :array
|
15
94
|
end
|
16
95
|
|
17
96
|
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
@@ -52,6 +131,12 @@ module ActiveRecord
|
|
52
131
|
column name, type, options
|
53
132
|
end
|
54
133
|
|
134
|
+
def new_column_definition(name, type, options) # :nodoc:
|
135
|
+
column = super
|
136
|
+
column.array = options[:array]
|
137
|
+
column
|
138
|
+
end
|
139
|
+
|
55
140
|
private
|
56
141
|
|
57
142
|
def create_column_definition(name, type)
|