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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a7e60cefd025afac2ee4f60f8206198ef8904c79
4
- data.tar.gz: ef72bd8f85e0657b328fe9e1c53c5f084e78ce6e
3
+ metadata.gz: 34f882890a917a408523cf0d811fec93c61f662b
4
+ data.tar.gz: b946f1a2a36571c0ddfb1e4fa8087396b590d5b4
5
5
  SHA512:
6
- metadata.gz: dbd01e3a518651f0028dfb7c49eccf6f650ae14d14480c551e9b6e8e6706abcc132a6043ed22e4f6745712a87b053c0df96dbc7254ff8e0622b2c515c78a29d8
7
- data.tar.gz: 52fed7e5a4c7ffb6bd4acc222afadb2b46a5e9fc7b19d75b104bb5a072c46dee2b0405ad0b95269f99e323fcbb5769c8e41e8d94d39beb23db95ffc47ccd9c6f
6
+ metadata.gz: a11030528067cd95e0e19f046db194214642be0bfe93d42394d9160e4aa6627ea420e4ee1684273aaaa56e782e587bdb1a67df18a0d52011ac04763d27daa2f0
7
+ data.tar.gz: 8638c106814e78f348adeccb9e0b896a53287da248993be0e546cd4c9a4e0da0f06769c6330dc6bfbfd641b41c9c3e565d0bb2e24b46233e4360e6c67c523b87
@@ -1,41 +1,36 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
- class RedshiftColumn < Column
4
- module ArrayParser
5
- private
6
- # Loads pg_array_parser if available. String parsing can be
7
- # performed quicker by a native extension, which will not create
8
- # a large amount of Ruby objects that will need to be garbage
9
- # collected. pg_array_parser has a C and Java extension
10
- begin
11
- require 'pg_array_parser'
12
- include PgArrayParser
13
- rescue LoadError
14
- def parse_pg_array(string)
15
- parse_data(string, 0)
3
+ module Redshift
4
+ module ArrayParser # :nodoc:
5
+
6
+ DOUBLE_QUOTE = '"'
7
+ BACKSLASH = "\\"
8
+ COMMA = ','
9
+ BRACKET_OPEN = '{'
10
+ BRACKET_CLOSE = '}'
11
+
12
+ def parse_pg_array(string) # :nodoc:
13
+ local_index = 0
14
+ array = []
15
+ while(local_index < string.length)
16
+ case string[local_index]
17
+ when BRACKET_OPEN
18
+ local_index,array = parse_array_contents(array, string, local_index + 1)
19
+ when BRACKET_CLOSE
20
+ return array
16
21
  end
22
+ local_index += 1
17
23
  end
18
24
 
19
- def parse_data(string, index)
20
- local_index = index
21
- array = []
22
- while(local_index < string.length)
23
- case string[local_index]
24
- when '{'
25
- local_index,array = parse_array_contents(array, string, local_index + 1)
26
- when '}'
27
- return array
28
- end
29
- local_index += 1
30
- end
25
+ array
26
+ end
31
27
 
32
- array
33
- end
28
+ private
34
29
 
35
30
  def parse_array_contents(array, string, index)
36
- is_escaping = false
37
- is_quoted = false
38
- was_quoted = false
31
+ is_escaping = false
32
+ is_quoted = false
33
+ was_quoted = false
39
34
  current_item = ''
40
35
 
41
36
  local_index = index
@@ -47,29 +42,29 @@ module ActiveRecord
47
42
  else
48
43
  if is_quoted
49
44
  case token
50
- when '"'
45
+ when DOUBLE_QUOTE
51
46
  is_quoted = false
52
47
  was_quoted = true
53
- when "\\"
48
+ when BACKSLASH
54
49
  is_escaping = true
55
50
  else
56
51
  current_item << token
57
52
  end
58
53
  else
59
54
  case token
60
- when "\\"
55
+ when BACKSLASH
61
56
  is_escaping = true
62
- when ','
57
+ when COMMA
63
58
  add_item_to_array(array, current_item, was_quoted)
64
59
  current_item = ''
65
60
  was_quoted = false
66
- when '"'
61
+ when DOUBLE_QUOTE
67
62
  is_quoted = true
68
- when '{'
63
+ when BRACKET_OPEN
69
64
  internal_items = []
70
65
  local_index,internal_items = parse_array_contents(internal_items, string, local_index + 1)
71
66
  array.push(internal_items)
72
- when '}'
67
+ when BRACKET_CLOSE
73
68
  add_item_to_array(array, current_item, was_quoted)
74
69
  return local_index,array
75
70
  else
@@ -84,8 +79,9 @@ module ActiveRecord
84
79
  end
85
80
 
86
81
  def add_item_to_array(array, current_item, quoted)
87
- if current_item.length == 0
88
- elsif !quoted && current_item == 'NULL'
82
+ return if !quoted && current_item.length == 0
83
+
84
+ if !quoted && current_item == 'NULL'
89
85
  array.push nil
90
86
  else
91
87
  array.push current_item
@@ -0,0 +1,10 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class RedshiftColumn < Column #:nodoc:
4
+ def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil)
5
+ super name, default, cast_type, sql_type, null
6
+ @default_function = default_function
7
+ end
8
+ end
9
+ end
10
+ end
@@ -1,6 +1,6 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
- class RedshiftAdapter < AbstractAdapter
3
+ module Redshift
4
4
  module DatabaseStatements
5
5
  def explain(arel, binds = [])
6
6
  sql = "EXPLAIN #{to_sql(arel, binds)}"
@@ -44,10 +44,32 @@ module ActiveRecord
44
44
  end
45
45
  end
46
46
 
47
+ def select_value(arel, name = nil, binds = [])
48
+ arel, binds = binds_from_relation arel, binds
49
+ sql = to_sql(arel, binds)
50
+ execute_and_clear(sql, name, binds) do |result|
51
+ result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0
52
+ end
53
+ end
54
+
55
+ def select_values(arel, name = nil)
56
+ arel, binds = binds_from_relation arel, []
57
+ sql = to_sql(arel, binds)
58
+ execute_and_clear(sql, name, binds) do |result|
59
+ if result.nfields > 0
60
+ result.column_values(0)
61
+ else
62
+ []
63
+ end
64
+ end
65
+ end
66
+
47
67
  # Executes a SELECT query and returns an array of rows. Each row is an
48
68
  # array of field values.
49
- def select_rows(sql, name = nil, bind = [])
50
- select_raw(sql, name).last
69
+ def select_rows(sql, name = nil, binds = [])
70
+ execute_and_clear(sql, name, binds) do |result|
71
+ result.values
72
+ end
51
73
  end
52
74
 
53
75
  # Executes an INSERT query and returns the new record's ID
@@ -72,6 +94,11 @@ module ActiveRecord
72
94
  super.insert
73
95
  end
74
96
 
97
+ # The internal PostgreSQL identifier of the money data type.
98
+ MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
99
+ # The internal PostgreSQL identifier of the BYTEA data type.
100
+ BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
101
+
75
102
  # create a 2D array representing the result set
76
103
  def result_as_array(res) #:nodoc:
77
104
  # check if we have any binary column and if they need escaping
@@ -129,39 +156,21 @@ module ActiveRecord
129
156
  end
130
157
  end
131
158
 
132
- def substitute_at(column, index)
133
- Arel::Nodes::BindParam.new "$#{index + 1}"
134
- end
135
-
136
159
  def exec_query(sql, name = 'SQL', binds = [])
137
- log(sql, name, binds) do
138
- result = binds.empty? ? exec_no_cache(sql, binds) :
139
- exec_cache(sql, binds)
140
-
160
+ execute_and_clear(sql, name, binds) do |result|
141
161
  types = {}
142
- result.fields.each_with_index do |fname, i|
162
+ fields = result.fields
163
+ fields.each_with_index do |fname, i|
143
164
  ftype = result.ftype i
144
165
  fmod = result.fmod i
145
- types[fname] = OID::TYPE_MAP.fetch(ftype, fmod) { |oid, mod|
146
- warn "unknown OID: #{fname}(#{oid}) (#{sql})"
147
- OID::Identity.new
148
- }
166
+ types[fname] = get_oid_type(ftype, fmod, fname)
149
167
  end
150
-
151
- ret = ActiveRecord::Result.new(result.fields, result.values, types)
152
- result.clear
153
- return ret
168
+ ActiveRecord::Result.new(fields, result.values, types)
154
169
  end
155
170
  end
156
171
 
157
172
  def exec_delete(sql, name = 'SQL', binds = [])
158
- log(sql, name, binds) do
159
- result = binds.empty? ? exec_no_cache(sql, binds) :
160
- exec_cache(sql, binds)
161
- affected = result.cmd_tuples
162
- result.clear
163
- affected
164
- end
173
+ execute_and_clear(sql, name, binds) {|result| result.cmd_tuples }
165
174
  end
166
175
  alias :exec_update :exec_delete
167
176
 
@@ -214,28 +223,9 @@ module ActiveRecord
214
223
  end
215
224
 
216
225
  # Aborts a transaction.
217
- def rollback_db_transaction
226
+ def exec_rollback_db_transaction
218
227
  execute "ROLLBACK"
219
228
  end
220
-
221
- def outside_transaction?
222
- message = "#outside_transaction? is deprecated. This method was only really used " \
223
- "internally, but you can use #transaction_open? instead."
224
- ActiveSupport::Deprecation.warn message
225
- @connection.transaction_status == PGconn::PQTRANS_IDLE
226
- end
227
-
228
- def create_savepoint
229
- execute("SAVEPOINT #{current_savepoint_name}")
230
- end
231
-
232
- def rollback_to_savepoint
233
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
234
- end
235
-
236
- def release_savepoint
237
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
238
- end
239
229
  end
240
230
  end
241
231
  end
@@ -1,365 +1,20 @@
1
- require 'active_record/connection_adapters/abstract_adapter'
1
+ require 'active_record/connection_adapters/redshift/oid/infinity'
2
2
 
3
- module ActiveRecord
4
- module ConnectionAdapters
5
- class RedshiftAdapter < AbstractAdapter
6
- module OID
7
- class Type
8
- def type; end
9
-
10
- def type_cast_for_write(value)
11
- value
12
- end
13
- end
14
-
15
- class Identity < Type
16
- def type_cast(value)
17
- value
18
- end
19
- end
20
-
21
- class Bit < Type
22
- def type_cast(value)
23
- if String === value
24
- ConnectionAdapters::RedshiftColumn.string_to_bit value
25
- else
26
- value
27
- end
28
- end
29
- end
30
-
31
- class Bytea < Type
32
- def type_cast(value)
33
- return if value.nil?
34
- PGconn.unescape_bytea value
35
- end
36
- end
37
-
38
- class Money < Type
39
- def type_cast(value)
40
- return if value.nil?
41
-
42
- # Because money output is formatted according to the locale, there are two
43
- # cases to consider (note the decimal separators):
44
- # (1) $12,345,678.12
45
- # (2) $12.345.678,12
46
-
47
- case value
48
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
49
- value.gsub!(/[^-\d.]/, '')
50
- when /^-?\D+[\d.]+,\d{2}$/ # (2)
51
- value.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
52
- end
53
-
54
- ConnectionAdapters::Column.value_to_decimal value
55
- end
56
- end
57
-
58
- class Vector < Type
59
- attr_reader :delim, :subtype
60
-
61
- # +delim+ corresponds to the `typdelim` column in the pg_types
62
- # table. +subtype+ is derived from the `typelem` column in the
63
- # pg_types table.
64
- def initialize(delim, subtype)
65
- @delim = delim
66
- @subtype = subtype
67
- end
68
-
69
- # FIXME: this should probably split on +delim+ and use +subtype+
70
- # to cast the values. Unfortunately, the current Rails behavior
71
- # is to just return the string.
72
- def type_cast(value)
73
- value
74
- end
75
- end
76
-
77
- class Point < Type
78
- def type_cast(value)
79
- if String === value
80
- ConnectionAdapters::RedshiftColumn.string_to_point value
81
- else
82
- value
83
- end
84
- end
85
- end
86
-
87
- class Array < Type
88
- attr_reader :subtype
89
- def initialize(subtype)
90
- @subtype = subtype
91
- end
92
-
93
- def type_cast(value)
94
- if String === value
95
- ConnectionAdapters::RedshiftColumn.string_to_array value, @subtype
96
- else
97
- value
98
- end
99
- end
100
- end
101
-
102
- class Range < Type
103
- attr_reader :subtype
104
- def initialize(subtype)
105
- @subtype = subtype
106
- end
107
-
108
- def extract_bounds(value)
109
- from, to = value[1..-2].split(',')
110
- {
111
- from: (value[1] == ',' || from == '-infinity') ? infinity(:negative => true) : from,
112
- to: (value[-2] == ',' || to == 'infinity') ? infinity : to,
113
- exclude_start: (value[0] == '('),
114
- exclude_end: (value[-1] == ')')
115
- }
116
- end
117
-
118
- def infinity(options = {})
119
- ::Float::INFINITY * (options[:negative] ? -1 : 1)
120
- end
121
-
122
- def infinity?(value)
123
- value.respond_to?(:infinite?) && value.infinite?
124
- end
125
-
126
- def to_integer(value)
127
- infinity?(value) ? value : value.to_i
128
- end
129
-
130
- def type_cast(value)
131
- return if value.nil? || value == 'empty'
132
- return value if value.is_a?(::Range)
133
-
134
- extracted = extract_bounds(value)
135
-
136
- case @subtype
137
- when :date
138
- from = ConnectionAdapters::Column.value_to_date(extracted[:from])
139
- from -= 1.day if extracted[:exclude_start]
140
- to = ConnectionAdapters::Column.value_to_date(extracted[:to])
141
- when :decimal
142
- from = BigDecimal.new(extracted[:from].to_s)
143
- # FIXME: add exclude start for ::Range, same for timestamp ranges
144
- to = BigDecimal.new(extracted[:to].to_s)
145
- when :time
146
- from = ConnectionAdapters::Column.string_to_time(extracted[:from])
147
- to = ConnectionAdapters::Column.string_to_time(extracted[:to])
148
- when :integer
149
- from = to_integer(extracted[:from]) rescue value ? 1 : 0
150
- from -= 1 if extracted[:exclude_start]
151
- to = to_integer(extracted[:to]) rescue value ? 1 : 0
152
- else
153
- return value
154
- end
155
-
156
- ::Range.new(from, to, extracted[:exclude_end])
157
- end
158
- end
159
-
160
- class Integer < Type
161
- def type_cast(value)
162
- return if value.nil?
163
-
164
- ConnectionAdapters::Column.value_to_integer value
165
- end
166
- end
167
-
168
- class Boolean < Type
169
- def type_cast(value)
170
- return if value.nil?
171
-
172
- ConnectionAdapters::Column.value_to_boolean value
173
- end
174
- end
175
-
176
- class Timestamp < Type
177
- def type; :timestamp; end
178
-
179
- def type_cast(value)
180
- return if value.nil?
181
-
182
- # FIXME: probably we can improve this since we know it is PG
183
- # specific
184
- ConnectionAdapters::RedshiftColumn.string_to_time value
185
- end
186
- end
187
-
188
- class Date < Type
189
- def type; :datetime; end
190
-
191
- def type_cast(value)
192
- return if value.nil?
3
+ require 'active_record/connection_adapters/redshift/oid/date'
4
+ require 'active_record/connection_adapters/redshift/oid/date_time'
5
+ require 'active_record/connection_adapters/redshift/oid/decimal'
6
+ require 'active_record/connection_adapters/redshift/oid/float'
7
+ require 'active_record/connection_adapters/redshift/oid/integer'
8
+ require 'active_record/connection_adapters/redshift/oid/json'
9
+ require 'active_record/connection_adapters/redshift/oid/jsonb'
10
+ require 'active_record/connection_adapters/redshift/oid/time'
193
11
 
194
- # FIXME: probably we can improve this since we know it is PG
195
- # specific
196
- ConnectionAdapters::Column.value_to_date value
197
- end
198
- end
12
+ require 'active_record/connection_adapters/redshift/oid/type_map_initializer'
199
13
 
200
- class Time < Type
201
- def type_cast(value)
202
- return if value.nil?
203
-
204
- # FIXME: probably we can improve this since we know it is PG
205
- # specific
206
- ConnectionAdapters::Column.string_to_dummy_time value
207
- end
208
- end
209
-
210
- class Float < Type
211
- def type_cast(value)
212
- return if value.nil?
213
-
214
- value.to_f
215
- end
216
- end
217
-
218
- class Decimal < Type
219
- def type_cast(value)
220
- return if value.nil?
221
-
222
- ConnectionAdapters::Column.value_to_decimal value
223
- end
224
- end
225
-
226
- class Hstore < Type
227
- def type_cast(value)
228
- return if value.nil?
229
-
230
- ConnectionAdapters::RedshiftColumn.string_to_hstore value
231
- end
232
- end
233
-
234
- class Cidr < Type
235
- def type_cast(value)
236
- return if value.nil?
237
-
238
- ConnectionAdapters::RedshiftColumn.string_to_cidr value
239
- end
240
- end
241
-
242
- class Json < Type
243
- def type_cast(value)
244
- return if value.nil?
245
-
246
- ConnectionAdapters::RedshiftColumn.string_to_json value
247
- end
248
- end
249
-
250
- class TypeMap
251
- def initialize
252
- @mapping = {}
253
- end
254
-
255
- def []=(oid, type)
256
- @mapping[oid] = type
257
- end
258
-
259
- def [](oid)
260
- @mapping[oid]
261
- end
262
-
263
- def clear
264
- @mapping.clear
265
- end
266
-
267
- def key?(oid)
268
- @mapping.key? oid
269
- end
270
-
271
- def fetch(ftype, fmod)
272
- # The type for the numeric depends on the width of the field,
273
- # so we'll do something special here.
274
- #
275
- # When dealing with decimal columns:
276
- #
277
- # places after decimal = fmod - 4 & 0xffff
278
- # places before decimal = (fmod - 4) >> 16 & 0xffff
279
- if ftype == 1700 && (fmod - 4 & 0xffff).zero?
280
- ftype = 23
281
- end
282
-
283
- @mapping.fetch(ftype) { |oid| yield oid, fmod }
284
- end
285
- end
286
-
287
- TYPE_MAP = TypeMap.new # :nodoc:
288
-
289
- # When the PG adapter connects, the pg_type table is queried. The
290
- # key of this hash maps to the `typname` column from the table.
291
- # TYPE_MAP is then dynamically built with oids as the key and type
292
- # objects as values.
293
- NAMES = Hash.new { |h,k| # :nodoc:
294
- h[k] = OID::Identity.new
295
- }
296
-
297
- # Register an OID type named +name+ with a typcasting object in
298
- # +type+. +name+ should correspond to the `typname` column in
299
- # the `pg_type` table.
300
- def self.register_type(name, type)
301
- NAMES[name] = type
302
- end
303
-
304
- # Alias the +old+ type to the +new+ type.
305
- def self.alias_type(new, old)
306
- NAMES[new] = NAMES[old]
307
- end
308
-
309
- # Is +name+ a registered type?
310
- def self.registered_type?(name)
311
- NAMES.key? name
312
- end
313
-
314
- register_type 'int2', OID::Integer.new
315
- alias_type 'int4', 'int2'
316
- alias_type 'int8', 'int2'
317
- alias_type 'oid', 'int2'
318
-
319
- register_type 'daterange', OID::Range.new(:date)
320
- register_type 'numrange', OID::Range.new(:decimal)
321
- register_type 'tsrange', OID::Range.new(:time)
322
- register_type 'int4range', OID::Range.new(:integer)
323
- alias_type 'tstzrange', 'tsrange'
324
- alias_type 'int8range', 'int4range'
325
-
326
- register_type 'numeric', OID::Decimal.new
327
- register_type 'text', OID::Identity.new
328
- alias_type 'varchar', 'text'
329
- alias_type 'char', 'text'
330
- alias_type 'bpchar', 'text'
331
- alias_type 'xml', 'text'
332
-
333
- # FIXME: why are we keeping these types as strings?
334
- alias_type 'tsvector', 'text'
335
- alias_type 'interval', 'text'
336
- alias_type 'macaddr', 'text'
337
- alias_type 'uuid', 'text'
338
-
339
- register_type 'money', OID::Money.new
340
- register_type 'bytea', OID::Bytea.new
341
- register_type 'bool', OID::Boolean.new
342
- register_type 'bit', OID::Bit.new
343
- register_type 'varbit', OID::Bit.new
344
-
345
- register_type 'float4', OID::Float.new
346
- alias_type 'float8', 'float4'
347
-
348
- register_type 'timestamp', OID::Timestamp.new
349
- register_type 'timestamptz', OID::Timestamp.new
350
- register_type 'date', OID::Date.new
351
- register_type 'time', OID::Time.new
352
-
353
- register_type 'path', OID::Identity.new
354
- register_type 'point', OID::Point.new
355
- register_type 'polygon', OID::Identity.new
356
- register_type 'circle', OID::Identity.new
357
- register_type 'hstore', OID::Hstore.new
358
- register_type 'json', OID::Json.new
359
- register_type 'ltree', OID::Identity.new
360
-
361
- register_type 'cidr', OID::Cidr.new
362
- alias_type 'inet', 'cidr'
14
+ module ActiveRecord
15
+ module ConnectionAdapters
16
+ module Redshift
17
+ module OID # :nodoc:
363
18
  end
364
19
  end
365
20
  end