activerecord-redshift-adapter-ng 0.9.0 → 0.9.1

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.
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)