activerecord4-redshift-adapter 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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