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.
Files changed (23) hide show
  1. checksums.yaml +4 -4
  2. data/lib/active_record/connection_adapters/redshift/array_parser.rb +35 -39
  3. data/lib/active_record/connection_adapters/redshift/column.rb +10 -0
  4. data/lib/active_record/connection_adapters/redshift/database_statements.rb +37 -47
  5. data/lib/active_record/connection_adapters/redshift/oid.rb +14 -359
  6. data/lib/active_record/connection_adapters/redshift/oid/date.rb +11 -0
  7. data/lib/active_record/connection_adapters/redshift/oid/date_time.rb +36 -0
  8. data/lib/active_record/connection_adapters/redshift/oid/decimal.rb +13 -0
  9. data/lib/active_record/connection_adapters/redshift/oid/float.rb +21 -0
  10. data/lib/active_record/connection_adapters/redshift/oid/infinity.rb +13 -0
  11. data/lib/active_record/connection_adapters/redshift/oid/integer.rb +11 -0
  12. data/lib/active_record/connection_adapters/redshift/oid/json.rb +35 -0
  13. data/lib/active_record/connection_adapters/redshift/oid/jsonb.rb +23 -0
  14. data/lib/active_record/connection_adapters/redshift/oid/time.rb +11 -0
  15. data/lib/active_record/connection_adapters/redshift/oid/type_map_initializer.rb +63 -0
  16. data/lib/active_record/connection_adapters/redshift/quoting.rb +45 -119
  17. data/lib/active_record/connection_adapters/redshift/referential_integrity.rb +4 -19
  18. data/lib/active_record/connection_adapters/redshift/schema_definitions.rb +73 -0
  19. data/lib/active_record/connection_adapters/redshift/schema_statements.rb +141 -76
  20. data/lib/active_record/connection_adapters/redshift/utils.rb +77 -0
  21. data/lib/active_record/connection_adapters/redshift_adapter.rb +252 -496
  22. metadata +17 -11
  23. data/lib/active_record/connection_adapters/redshift/cast.rb +0 -156
@@ -0,0 +1,11 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ module OID # :nodoc:
5
+ class Date < Type::Date # :nodoc:
6
+ include Infinity
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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,13 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ module OID # :nodoc:
5
+ module Infinity # :nodoc:
6
+ def infinity(options = {})
7
+ options[:negative] ? -::Float::INFINITY : ::Float::INFINITY
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ module OID # :nodoc:
5
+ class Integer < Type::Integer # :nodoc:
6
+ include Infinity
7
+ end
8
+ end
9
+ end
10
+ end
11
+ 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,11 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ module OID # :nodoc:
5
+ class Time < Type::Time # :nodoc:
6
+ include Infinity
7
+ end
8
+ end
9
+ end
10
+ end
11
+ 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
- class RedshiftAdapter < AbstractAdapter
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
- PGconn.escape_bytea(value) if value
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
- PGconn.unescape_bytea(value) if value
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
- schema, name_part = extract_pg_identifier_from_name(name.to_s)
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 < 0
165
- result = result.sub(/^-/, "") + " BC"
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