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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +0 -33
  3. data/README.md +12 -16
  4. data/lib/active_record/connection_adapters/redshift/column.rb +11 -1
  5. data/lib/active_record/connection_adapters/redshift/oid.rb +15 -0
  6. data/lib/active_record/connection_adapters/redshift/oid/array.rb +96 -0
  7. data/lib/active_record/connection_adapters/redshift/oid/bit.rb +52 -0
  8. data/lib/active_record/connection_adapters/redshift/oid/bit_varying.rb +13 -0
  9. data/lib/active_record/connection_adapters/redshift/oid/bytea.rb +14 -0
  10. data/lib/active_record/connection_adapters/redshift/oid/cidr.rb +46 -0
  11. data/lib/active_record/connection_adapters/redshift/oid/date_time.rb +0 -9
  12. data/lib/active_record/connection_adapters/redshift/oid/enum.rb +17 -0
  13. data/lib/active_record/connection_adapters/redshift/oid/hstore.rb +59 -0
  14. data/lib/active_record/connection_adapters/redshift/oid/inet.rb +13 -0
  15. data/lib/active_record/connection_adapters/redshift/oid/json.rb +1 -1
  16. data/lib/active_record/connection_adapters/redshift/oid/money.rb +43 -0
  17. data/lib/active_record/connection_adapters/redshift/oid/point.rb +43 -0
  18. data/lib/active_record/connection_adapters/redshift/oid/range.rb +76 -0
  19. data/lib/active_record/connection_adapters/redshift/oid/specialized_string.rb +15 -0
  20. data/lib/active_record/connection_adapters/redshift/oid/type_map_initializer.rb +42 -20
  21. data/lib/active_record/connection_adapters/redshift/oid/uuid.rb +28 -0
  22. data/lib/active_record/connection_adapters/redshift/oid/vector.rb +26 -0
  23. data/lib/active_record/connection_adapters/redshift/oid/xml.rb +28 -0
  24. data/lib/active_record/connection_adapters/redshift/quoting.rb +10 -0
  25. data/lib/active_record/connection_adapters/redshift/referential_integrity.rb +15 -0
  26. data/lib/active_record/connection_adapters/redshift/schema_definitions.rb +85 -0
  27. data/lib/active_record/connection_adapters/redshift/schema_statements.rb +171 -8
  28. data/lib/active_record/connection_adapters/redshift/utils.rb +4 -15
  29. data/lib/active_record/connection_adapters/redshift_adapter.rb +119 -38
  30. metadata +26 -9
@@ -0,0 +1,13 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ module OID # :nodoc:
5
+ class Inet < Cidr # :nodoc:
6
+ def type
7
+ :inet
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
 
12
12
  def type_cast_from_database(value)
13
13
  if value.is_a?(::String)
14
- ::ActiveSupport::JSON.decode(value) rescue nil
14
+ ::ActiveSupport::JSON.decode(value)
15
15
  else
16
16
  super
17
17
  end
@@ -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
@@ -0,0 +1,15 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ module OID # :nodoc:
5
+ class SpecializedString < Type::String # :nodoc:
6
+ attr_reader :type
7
+
8
+ def initialize(type)
9
+ @type = type
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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 an integer representing the type.
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'.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 }
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 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
+ 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
- @store.register_type(oid, oid_type)
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)