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
@@ -0,0 +1,77 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ # Value Object to hold a schema qualified name.
5
+ # This is usually the name of a PostgreSQL relation but it can also represent
6
+ # schema qualified type names. +schema+ and +identifier+ are unquoted to prevent
7
+ # double quoting.
8
+ class Name # :nodoc:
9
+ SEPARATOR = "."
10
+ attr_reader :schema, :identifier
11
+
12
+ def initialize(schema, identifier)
13
+ @schema, @identifier = unquote(schema), unquote(identifier)
14
+ end
15
+
16
+ def to_s
17
+ parts.join SEPARATOR
18
+ end
19
+
20
+ def quoted
21
+ if schema
22
+ PGconn.quote_ident(schema) << SEPARATOR << PGconn.quote_ident(identifier)
23
+ else
24
+ PGconn.quote_ident(identifier)
25
+ end
26
+ end
27
+
28
+ def ==(o)
29
+ o.class == self.class && o.parts == parts
30
+ end
31
+ alias_method :eql?, :==
32
+
33
+ def hash
34
+ parts.hash
35
+ end
36
+
37
+ protected
38
+ def unquote(part)
39
+ if part && part.start_with?('"')
40
+ part[1..-2]
41
+ else
42
+ part
43
+ end
44
+ end
45
+
46
+ def parts
47
+ @parts ||= [@schema, @identifier].compact
48
+ end
49
+ end
50
+
51
+ module Utils # :nodoc:
52
+ extend self
53
+
54
+ # Returns an instance of <tt>ActiveRecord::ConnectionAdapters::PostgreSQL::Name</tt>
55
+ # extracted from +string+.
56
+ # +schema+ is nil if not specified in +string+.
57
+ # +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+)
58
+ # +string+ supports the range of schema/table references understood by PostgreSQL, for example:
59
+ #
60
+ # * <tt>table_name</tt>
61
+ # * <tt>"table.name"</tt>
62
+ # * <tt>schema_name.table_name</tt>
63
+ # * <tt>schema_name."table.name"</tt>
64
+ # * <tt>"schema_name".table_name</tt>
65
+ # * <tt>"schema.name"."table name"</tt>
66
+ def extract_schema_qualified_name(string)
67
+ schema, table = string.scan(/[^".\s]+|"[^"]*"/)
68
+ if table.nil?
69
+ table = schema
70
+ schema = nil
71
+ end
72
+ Redshift::Name.new(schema, table)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,16 +1,19 @@
1
1
  require 'active_record/connection_adapters/abstract_adapter'
2
2
  require 'active_record/connection_adapters/statement_pool'
3
+
4
+ require 'active_record/connection_adapters/redshift/utils'
5
+ require 'active_record/connection_adapters/redshift/column'
3
6
  require 'active_record/connection_adapters/redshift/oid'
4
- require 'active_record/connection_adapters/redshift/cast'
5
- require 'active_record/connection_adapters/redshift/array_parser'
6
7
  require 'active_record/connection_adapters/redshift/quoting'
8
+ require 'active_record/connection_adapters/redshift/referential_integrity'
9
+ require 'active_record/connection_adapters/redshift/schema_definitions'
7
10
  require 'active_record/connection_adapters/redshift/schema_statements'
8
11
  require 'active_record/connection_adapters/redshift/database_statements'
9
- require 'active_record/connection_adapters/redshift/referential_integrity'
12
+
10
13
  require 'arel/visitors/bind_visitor'
11
14
 
12
15
  # Make sure we're using pg high enough for PGResult#values
13
- gem 'pg', '~> 0.11'
16
+ gem 'pg', '> 0.15'
14
17
  require 'pg'
15
18
 
16
19
  require 'ipaddr'
@@ -20,8 +23,8 @@ module ActiveRecord
20
23
  RS_VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
21
24
  :client_encoding, :options, :application_name, :fallback_application_name,
22
25
  :keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
23
- :tty, :sslmode, :requiressl, :sslcert, :sslkey, :sslrootcert, :sslcrl,
24
- :requirepeer, :krbsrvname, :gsslib, :service]
26
+ :tty, :sslmode, :requiressl, :sslcompression, :sslcert, :sslkey,
27
+ :sslrootcert, :sslcrl, :requirepeer, :krbsrvname, :gsslib, :service]
25
28
 
26
29
  # Establishes a connection to the database that's used by all Active Record objects
27
30
  def redshift_connection(config)
@@ -43,194 +46,6 @@ module ActiveRecord
43
46
  end
44
47
 
45
48
  module ConnectionAdapters
46
- # PostgreSQL-specific extensions to column definitions in a table.
47
- class RedshiftColumn < Column #:nodoc:
48
- attr_accessor :array
49
- # Instantiates a new PostgreSQL column definition in a table.
50
- def initialize(name, default, oid_type, sql_type = nil, null = true)
51
- @oid_type = oid_type
52
- if sql_type =~ /\[\]$/
53
- @array = true
54
- super(name, self.class.extract_value_from_default(default), sql_type[0..sql_type.length - 3], null)
55
- else
56
- @array = false
57
- super(name, self.class.extract_value_from_default(default), sql_type, null)
58
- end
59
- end
60
-
61
- # :stopdoc:
62
- class << self
63
- include ConnectionAdapters::RedshiftColumn::Cast
64
- include ConnectionAdapters::RedshiftColumn::ArrayParser
65
- attr_accessor :money_precision
66
- end
67
- # :startdoc:
68
-
69
- # Extracts the value from a PostgreSQL column default definition.
70
- def self.extract_value_from_default(default)
71
- # This is a performance optimization for Ruby 1.9.2 in development.
72
- # If the value is nil, we return nil straight away without checking
73
- # the regular expressions. If we check each regular expression,
74
- # Regexp#=== will call NilClass#to_str, which will trigger
75
- # method_missing (defined by whiny nil in ActiveSupport) which
76
- # makes this method very very slow.
77
- return default unless default
78
-
79
- case default
80
- when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
81
- $1
82
- # Numeric types
83
- when /\A\(?(-?\d+(\.\d*)?\)?)\z/
84
- $1
85
- # Character types
86
- when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
87
- $1
88
- # Binary data types
89
- when /\A'(.*)'::bytea\z/m
90
- $1
91
- # Date/time types
92
- when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
93
- $1
94
- when /\A'(.*)'::interval\z/
95
- $1
96
- # Boolean type
97
- when 'true'
98
- true
99
- when 'false'
100
- false
101
- # Geometric types
102
- when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
103
- $1
104
- # Network address types
105
- when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
106
- $1
107
- # Bit string types
108
- when /\AB'(.*)'::"?bit(?: varying)?"?\z/
109
- $1
110
- # XML type
111
- when /\A'(.*)'::xml\z/m
112
- $1
113
- # Arrays
114
- when /\A'(.*)'::"?\D+"?\[\]\z/
115
- $1
116
- # Hstore
117
- when /\A'(.*)'::hstore\z/
118
- $1
119
- # JSON
120
- when /\A'(.*)'::json\z/
121
- $1
122
- # Object identifier types
123
- when /\A-?\d+\z/
124
- $1
125
- else
126
- # Anything else is blank, some user type, or some function
127
- # and we can't know the value of that, so return nil.
128
- nil
129
- end
130
- end
131
-
132
- def type_cast(value)
133
- return if value.nil?
134
- return super if encoded?
135
-
136
- @oid_type.type_cast value
137
- end
138
-
139
- private
140
-
141
- def extract_limit(sql_type)
142
- case sql_type
143
- when /^bigint/i; 8
144
- when /^smallint/i; 2
145
- when /^timestamp/i; nil
146
- else super
147
- end
148
- end
149
-
150
- # Extracts the scale from PostgreSQL-specific data types.
151
- def extract_scale(sql_type)
152
- # Money type has a fixed scale of 2.
153
- sql_type =~ /^money/ ? 2 : super
154
- end
155
-
156
- # Extracts the precision from PostgreSQL-specific data types.
157
- def extract_precision(sql_type)
158
- if sql_type == 'money'
159
- self.class.money_precision
160
- elsif sql_type =~ /timestamp/i
161
- $1.to_i if sql_type =~ /\((\d+)\)/
162
- else
163
- super
164
- end
165
- end
166
-
167
- # Maps PostgreSQL-specific data types to logical Rails types.
168
- def simplified_type(field_type)
169
- case field_type
170
- # Numeric and monetary types
171
- when /^(?:real|double precision)$/
172
- :float
173
- # Monetary types
174
- when 'money'
175
- :decimal
176
- when 'hstore'
177
- :hstore
178
- when 'ltree'
179
- :ltree
180
- # Network address types
181
- when 'inet'
182
- :inet
183
- when 'cidr'
184
- :cidr
185
- when 'macaddr'
186
- :macaddr
187
- # Character types
188
- when /^(?:character varying|bpchar)(?:\(\d+\))?$/
189
- :string
190
- # Binary data types
191
- when 'bytea'
192
- :binary
193
- # Date/time types
194
- when /^timestamp with(?:out)? time zone$/
195
- :datetime
196
- when /^interval(?:|\(\d+\))$/
197
- :string
198
- # Geometric types
199
- when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
200
- :string
201
- # Bit strings
202
- when /^bit(?: varying)?(?:\(\d+\))?$/
203
- :string
204
- # XML type
205
- when 'xml'
206
- :xml
207
- # tsvector type
208
- when 'tsvector'
209
- :tsvector
210
- # Arrays
211
- when /^\D+\[\]$/
212
- :string
213
- # Object identifier types
214
- when 'oid'
215
- :integer
216
- # UUID type
217
- when 'uuid'
218
- :uuid
219
- # JSON type
220
- when 'json'
221
- :json
222
- # Small and big integer types
223
- when /^(?:small|big)int$/
224
- :integer
225
- when /(num|date|tstz|ts|int4|int8)range$/
226
- field_type.to_sym
227
- # Pass through all types that are not specific to PostgreSQL.
228
- else
229
- super
230
- end
231
- end
232
- end
233
-
234
49
  # The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
235
50
  #
236
51
  # Options:
@@ -249,7 +64,7 @@ module ActiveRecord
249
64
  # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
250
65
  # * <tt>:variables</tt> - An optional hash of additional parameters that
251
66
  # will be used in <tt>SET SESSION key = val</tt> calls on the connection.
252
- # * <tt>:insert_returning</tt> - does nothing for Redshift.
67
+ # * <tt>:insert_returning</tt> - Does nothing for Redshift.
253
68
  #
254
69
  # Any further options are used as connection parameters to libpq. See
255
70
  # http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
@@ -258,187 +73,44 @@ module ActiveRecord
258
73
  # In addition, default connection parameters of libpq can be set per environment variables.
259
74
  # See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .
260
75
  class RedshiftAdapter < AbstractAdapter
261
- class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
262
- attr_accessor :array
263
- end
264
-
265
- module ColumnMethods
266
- def xml(*args)
267
- options = args.extract_options!
268
- column(args[0], 'xml', options)
269
- end
270
-
271
- def tsvector(*args)
272
- options = args.extract_options!
273
- column(args[0], 'tsvector', options)
274
- end
275
-
276
- def int4range(name, options = {})
277
- column(name, 'int4range', options)
278
- end
279
-
280
- def int8range(name, options = {})
281
- column(name, 'int8range', options)
282
- end
283
-
284
- def tsrange(name, options = {})
285
- column(name, 'tsrange', options)
286
- end
287
-
288
- def tstzrange(name, options = {})
289
- column(name, 'tstzrange', options)
290
- end
291
-
292
- def numrange(name, options = {})
293
- column(name, 'numrange', options)
294
- end
295
-
296
- def daterange(name, options = {})
297
- column(name, 'daterange', options)
298
- end
299
-
300
- def hstore(name, options = {})
301
- column(name, 'hstore', options)
302
- end
303
-
304
- def ltree(name, options = {})
305
- column(name, 'ltree', options)
306
- end
307
-
308
- def inet(name, options = {})
309
- column(name, 'inet', options)
310
- end
311
-
312
- def cidr(name, options = {})
313
- column(name, 'cidr', options)
314
- end
315
-
316
- def macaddr(name, options = {})
317
- column(name, 'macaddr', options)
318
- end
319
-
320
- def uuid(name, options = {})
321
- column(name, 'uuid', options)
322
- end
323
-
324
- def json(name, options = {})
325
- column(name, 'json', options)
326
- end
327
- end
328
-
329
- class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
330
- include ColumnMethods
331
-
332
- # Defines the primary key field.
333
- # Use of the native PostgreSQL UUID type is supported, and can be used
334
- # by defining your tables as such:
335
- #
336
- # create_table :stuffs, id: :uuid do |t|
337
- # t.string :content
338
- # t.timestamps
339
- # end
340
- #
341
- # By default, this will use the +uuid_generate_v4()+ function from the
342
- # +uuid-ossp+ extension, which MUST be enabled on your databse. To enable
343
- # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
344
- # migrations To use a UUID primary key without +uuid-ossp+ enabled, you can
345
- # set the +:default+ option to nil:
346
- #
347
- # create_table :stuffs, id: false do |t|
348
- # t.primary_key :id, :uuid, default: nil
349
- # t.uuid :foo_id
350
- # t.timestamps
351
- # end
352
- #
353
- # You may also pass a different UUID generation function from +uuid-ossp+
354
- # or another library.
355
- #
356
- # Note that setting the UUID primary key default value to +nil+
357
- # will require you to assure that you always provide a UUID value
358
- # before saving a record (as primary keys cannot be nil). This might be
359
- # done via the SecureRandom.uuid method and a +before_save+ callback,
360
- # for instance.
361
- def primary_key(name, type = :primary_key, options = {})
362
- return super unless type == :uuid
363
- options[:default] = options.fetch(:default, 'uuid_generate_v4()')
364
- options[:primary_key] = true
365
- column name, type, options
366
- end
367
-
368
- def column(name, type = nil, options = {})
369
- super
370
- column = self[name]
371
- column.array = options[:array]
372
-
373
- self
374
- end
375
-
376
- def xml(options = {})
377
- column(args[0], :text, options)
378
- end
379
-
380
- private
381
-
382
- def create_column_definition(name, type)
383
- ColumnDefinition.new name, type
384
- end
385
- end
386
-
387
- class Table < ActiveRecord::ConnectionAdapters::Table
388
- include ColumnMethods
389
- end
390
-
391
- ADAPTER_NAME = 'Redshift'
76
+ ADAPTER_NAME = 'Redshift'.freeze
392
77
 
393
78
  NATIVE_DATABASE_TYPES = {
394
- primary_key: "serial primary key",
395
- string: { name: "character varying", limit: 255 },
79
+ primary_key: "integer primary key",
80
+ string: { name: "character varying" },
396
81
  text: { name: "text" },
397
82
  integer: { name: "integer" },
398
83
  float: { name: "float" },
399
84
  decimal: { name: "decimal" },
400
85
  datetime: { name: "timestamp" },
401
- timestamp: { name: "timestamp" },
402
86
  time: { name: "time" },
403
87
  date: { name: "date" },
404
- daterange: { name: "daterange" },
405
- numrange: { name: "numrange" },
406
- tsrange: { name: "tsrange" },
407
- tstzrange: { name: "tstzrange" },
408
- int4range: { name: "int4range" },
409
- int8range: { name: "int8range" },
410
- binary: { name: "bytea" },
411
- boolean: { name: "boolean" },
412
- xml: { name: "xml" },
413
- tsvector: { name: "tsvector" },
414
- hstore: { name: "hstore" },
415
- inet: { name: "inet" },
416
- cidr: { name: "cidr" },
417
- macaddr: { name: "macaddr" },
418
- uuid: { name: "uuid" },
88
+ bigint: { name: "bigint" },
419
89
  json: { name: "json" },
420
- ltree: { name: "ltree" }
90
+ jsonb: { name: "jsonb" }
421
91
  }
422
92
 
423
- include Quoting
424
- include ReferentialIntegrity
425
- include SchemaStatements
426
- include DatabaseStatements
93
+ OID = Redshift::OID #:nodoc:
94
+
95
+ include Redshift::Quoting
96
+ include Redshift::ReferentialIntegrity
97
+ include Redshift::SchemaStatements
98
+ include Redshift::DatabaseStatements
427
99
 
428
- # Returns 'PostgreSQL' as adapter name for identification purposes.
429
- def adapter_name
430
- ADAPTER_NAME
100
+ def schema_creation # :nodoc:
101
+ Redshift::SchemaCreation.new self
431
102
  end
432
103
 
433
- # Adds `:array` option to the default set provided by the
104
+ # Adds +:array+ option to the default set provided by the
434
105
  # AbstractAdapter
435
- def prepare_column_options(column, types)
106
+ def prepare_column_options(column, types) # :nodoc:
436
107
  spec = super
437
108
  spec[:array] = 'true' if column.respond_to?(:array) && column.array
109
+ spec[:default] = "\"#{column.default_function}\"" if column.default_function
438
110
  spec
439
111
  end
440
112
 
441
- # Adds `:array` as a valid migration key
113
+ # Adds +:array+ as a valid migration key
442
114
  def migration_keys
443
115
  super + [:array]
444
116
  end
@@ -450,14 +122,22 @@ module ActiveRecord
450
122
  end
451
123
 
452
124
  def supports_index_sort_order?
453
- true
125
+ false
454
126
  end
455
127
 
456
128
  def supports_partial_index?
457
- true
129
+ false
458
130
  end
459
131
 
460
132
  def supports_transaction_isolation?
133
+ false
134
+ end
135
+
136
+ def supports_foreign_keys?
137
+ true
138
+ end
139
+
140
+ def supports_views?
461
141
  true
462
142
  end
463
143
 
@@ -518,21 +198,13 @@ module ActiveRecord
518
198
  end
519
199
  end
520
200
 
521
- class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
522
- include Arel::Visitors::BindVisitor
523
- end
524
-
525
201
  # Initializes and connects a PostgreSQL adapter.
526
202
  def initialize(connection, logger, connection_parameters, config)
527
203
  super(connection, logger)
528
204
 
529
- if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
530
- @visitor = Arel::Visitors::PostgreSQL.new self
531
- else
532
- @visitor = unprepared_visitor
533
- end
205
+ @visitor = Arel::Visitors::PostgreSQL.new self
206
+ @prepared_statements = false
534
207
 
535
- connection_parameters.delete :prepared_statements
536
208
  @connection_parameters, @config = connection_parameters, config
537
209
 
538
210
  # @local_tz is initialized as nil to avoid warnings when connect tries to use it
@@ -543,7 +215,8 @@ module ActiveRecord
543
215
  @statements = StatementPool.new @connection,
544
216
  self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
545
217
 
546
- initialize_type_map
218
+ @type_map = Type::HashLookupTypeMap.new
219
+ initialize_type_map(type_map)
547
220
  @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
548
221
  @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : false
549
222
  end
@@ -553,9 +226,14 @@ module ActiveRecord
553
226
  @statements.clear
554
227
  end
555
228
 
229
+ def truncate(table_name, name = nil)
230
+ exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, []
231
+ end
232
+
556
233
  # Is this connection alive and ready for queries?
557
234
  def active?
558
- @connection.connect_poll != PG::PGRES_POLLING_FAILED
235
+ @connection.query 'SELECT 1'
236
+ true
559
237
  rescue PGError
560
238
  false
561
239
  end
@@ -569,7 +247,12 @@ module ActiveRecord
569
247
 
570
248
  def reset!
571
249
  clear_cache!
572
- super
250
+ reset_transaction
251
+ unless @connection.transaction_status == ::PG::PQTRANS_IDLE
252
+ @connection.query 'ROLLBACK'
253
+ end
254
+ @connection.query 'DISCARD ALL'
255
+ configure_connection
573
256
  end
574
257
 
575
258
  # Disconnects from the database if already connected. Otherwise, this
@@ -593,38 +276,38 @@ module ActiveRecord
593
276
  true
594
277
  end
595
278
 
596
- def supports_insert_with_returning?
597
- false
279
+ # Enable standard-conforming strings if available.
280
+ def set_standard_conforming_strings
281
+ old, self.client_min_messages = client_min_messages, 'panic'
282
+ execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
283
+ ensure
284
+ self.client_min_messages = old
598
285
  end
599
286
 
600
287
  def supports_ddl_transactions?
601
288
  true
602
289
  end
603
290
 
604
- # Returns true, since this connection adapter supports savepoints.
605
- def supports_savepoints?
606
- false
607
- end
608
-
609
- def supports_import?
610
- true
611
- end
612
-
613
- # Returns true.
614
291
  def supports_explain?
615
292
  true
616
293
  end
617
294
 
618
- # Returns true if pg > 9.2
619
295
  def supports_extensions?
620
296
  false
621
297
  end
622
298
 
623
- # Range datatypes weren't introduced until PostgreSQL 9.2
624
299
  def supports_ranges?
625
300
  false
626
301
  end
627
302
 
303
+ def supports_materialized_views?
304
+ false
305
+ end
306
+
307
+ def supports_import?
308
+ true
309
+ end
310
+
628
311
  def enable_extension(name)
629
312
  end
630
313
 
@@ -635,9 +318,6 @@ module ActiveRecord
635
318
  false
636
319
  end
637
320
 
638
- #def extensions
639
- #end
640
-
641
321
  # Returns the configured supported identifier length supported by PostgreSQL
642
322
  def table_alias_length
643
323
  @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
@@ -649,25 +329,6 @@ module ActiveRecord
649
329
  exec_query "SET SESSION AUTHORIZATION #{user}"
650
330
  end
651
331
 
652
- module Utils
653
- extend self
654
-
655
- # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
656
- # +schema_name+ is nil if not specified in +name+.
657
- # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
658
- # +name+ supports the range of schema/table references understood by PostgreSQL, for example:
659
- #
660
- # * <tt>table_name</tt>
661
- # * <tt>"table.name"</tt>
662
- # * <tt>schema_name.table_name</tt>
663
- # * <tt>schema_name."table.name"</tt>
664
- # * <tt>"schema.name"."table name"</tt>
665
- def extract_schema_and_table(name)
666
- table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
667
- [schema, table]
668
- end
669
- end
670
-
671
332
  def use_insert_returning?
672
333
  false
673
334
  end
@@ -676,17 +337,32 @@ module ActiveRecord
676
337
  !native_database_types[type].nil?
677
338
  end
678
339
 
340
+ def update_table_definition(table_name, base) #:nodoc:
341
+ Redshift::Table.new(table_name, base)
342
+ end
343
+
344
+ def lookup_cast_type(sql_type) # :nodoc:
345
+ oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first['oid'].to_i
346
+ super(oid)
347
+ end
348
+
349
+ def column_name_for_operation(operation, node) # :nodoc:
350
+ OPERATION_ALIASES.fetch(operation) { operation.downcase }
351
+ end
352
+
353
+ OPERATION_ALIASES = { # :nodoc:
354
+ "maximum" => "max",
355
+ "minimum" => "min",
356
+ "average" => "avg",
357
+ }
358
+
679
359
  protected
680
360
 
681
361
  # Returns the version of the connected PostgreSQL server.
682
- def postgresql_version
362
+ def redshift_version
683
363
  @connection.server_version
684
364
  end
685
365
 
686
- # See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
687
- FOREIGN_KEY_VIOLATION = "23503"
688
- UNIQUE_VIOLATION = "23505"
689
-
690
366
  def translate_exception(exception, message)
691
367
  return exception unless exception.respond_to?(:result)
692
368
 
@@ -702,64 +378,170 @@ module ActiveRecord
702
378
 
703
379
  private
704
380
 
705
- def reload_type_map
706
- OID::TYPE_MAP.clear
707
- initialize_type_map
708
- end
381
+ def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
382
+ if !type_map.key?(oid)
383
+ load_additional_types(type_map, [oid])
384
+ end
709
385
 
710
- def initialize_type_map
711
- result = execute('SELECT oid, typname, typelem, typdelim, typinput FROM pg_type', 'SCHEMA')
712
- leaves, nodes = result.partition { |row| row['typelem'] == '0' }
386
+ type_map.fetch(oid, fmod, sql_type) {
387
+ warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
388
+ Type::Value.new.tap do |cast_type|
389
+ type_map.register_type(oid, cast_type)
390
+ end
391
+ }
392
+ end
393
+
394
+ def initialize_type_map(m) # :nodoc:
395
+ register_class_with_limit m, 'int2', OID::Integer
396
+ register_class_with_limit m, 'int4', OID::Integer
397
+ register_class_with_limit m, 'int8', OID::Integer
398
+ m.alias_type 'oid', 'int2'
399
+ m.register_type 'float4', OID::Float.new
400
+ m.alias_type 'float8', 'float4'
401
+ m.register_type 'text', Type::Text.new
402
+ register_class_with_limit m, 'varchar', Type::String
403
+ m.alias_type 'char', 'varchar'
404
+ m.alias_type 'name', 'varchar'
405
+ m.alias_type 'bpchar', 'varchar'
406
+ m.alias_type 'timestamptz', 'timestamp'
407
+ m.register_type 'date', OID::Date.new
408
+ m.register_type 'time', OID::Time.new
409
+
410
+ m.register_type 'json', OID::Json.new
411
+ m.register_type 'jsonb', OID::Jsonb.new
412
+
413
+ # FIXME: why are we keeping these types as strings?
414
+ m.alias_type 'interval', 'varchar'
415
+
416
+ m.register_type 'timestamp' do |_, _, sql_type|
417
+ precision = extract_precision(sql_type)
418
+ OID::DateTime.new(precision: precision)
419
+ end
713
420
 
714
- # populate the leaf nodes
715
- leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
716
- OID::TYPE_MAP[row['oid'].to_i] = OID::NAMES[row['typname']]
421
+ m.register_type 'numeric' do |_, fmod, sql_type|
422
+ precision = extract_precision(sql_type)
423
+ scale = extract_scale(sql_type)
424
+
425
+ # The type for the numeric depends on the width of the field,
426
+ # so we'll do something special here.
427
+ #
428
+ # When dealing with decimal columns:
429
+ #
430
+ # places after decimal = fmod - 4 & 0xffff
431
+ # places before decimal = (fmod - 4) >> 16 & 0xffff
432
+ if fmod && (fmod - 4 & 0xffff).zero?
433
+ # FIXME: Remove this class, and the second argument to
434
+ # lookups on PG
435
+ Type::DecimalWithoutScale.new(precision: precision)
436
+ else
437
+ OID::Decimal.new(precision: precision, scale: scale)
438
+ end
717
439
  end
718
440
 
719
- arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
441
+ load_additional_types(m)
442
+ end
720
443
 
721
- # populate composite types
722
- nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
723
- if OID.registered_type? row['typname']
724
- # this composite type is explicitly registered
725
- vector = OID::NAMES[row['typname']]
444
+ def extract_limit(sql_type) # :nodoc:
445
+ case sql_type
446
+ when /^bigint/i, /^int8/i
447
+ 8
448
+ when /^smallint/i
449
+ 2
450
+ else
451
+ super
452
+ end
453
+ end
454
+
455
+ # Extracts the value from a PostgreSQL column default definition.
456
+ def extract_value_from_default(oid, default) # :nodoc:
457
+ case default
458
+ # Quoted types
459
+ when /\A[\(B]?'(.*)'::/m
460
+ $1.gsub(/''/, "'")
461
+ # Boolean types
462
+ when 'true', 'false'
463
+ default
464
+ # Numeric types
465
+ when /\A\(?(-?\d+(\.\d*)?)\)?\z/
466
+ $1
467
+ # Object identifier types
468
+ when /\A-?\d+\z/
469
+ $1
726
470
  else
727
- # use the default for composite types
728
- vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
729
- end
471
+ # Anything else is blank, some user type, or some function
472
+ # and we can't know the value of that, so return nil.
473
+ nil
474
+ end
475
+ end
476
+
477
+ def extract_default_function(default_value, default) # :nodoc:
478
+ default if has_default_function?(default_value, default)
479
+ end
480
+
481
+ def has_default_function?(default_value, default) # :nodoc:
482
+ !default_value && (%r{\w+\(.*\)} === default)
483
+ end
484
+
485
+ def load_additional_types(type_map, oids = nil) # :nodoc:
486
+ initializer = OID::TypeMapInitializer.new(type_map)
487
+
488
+ if supports_ranges?
489
+ query = <<-SQL
490
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
491
+ FROM pg_type as t
492
+ LEFT JOIN pg_range as r ON oid = rngtypid
493
+ SQL
494
+ else
495
+ query = <<-SQL
496
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
497
+ FROM pg_type as t
498
+ SQL
499
+ end
730
500
 
731
- OID::TYPE_MAP[row['oid'].to_i] = vector
501
+ if oids
502
+ query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
732
503
  end
733
504
 
734
- # populate array types
735
- arrays.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
736
- array = OID::Array.new OID::TYPE_MAP[row['typelem'].to_i]
737
- OID::TYPE_MAP[row['oid'].to_i] = array
505
+ execute_and_clear(query, 'SCHEMA', []) do |records|
506
+ initializer.run(records)
738
507
  end
739
508
  end
740
509
 
741
- FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
510
+ FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
511
+
512
+ def execute_and_clear(sql, name, binds)
513
+ result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
514
+ exec_cache(sql, name, binds)
515
+ ret = yield result
516
+ result.clear
517
+ ret
518
+ end
742
519
 
743
- def exec_no_cache(sql, binds)
744
- @connection.async_exec(sql, [])
520
+ def exec_no_cache(sql, name, binds)
521
+ log(sql, name, binds) { @connection.async_exec(sql, []) }
745
522
  end
746
523
 
747
- def exec_cache(sql, binds)
748
- stmt_key = prepare_statement sql
749
-
750
- # Clear the queue
751
- @connection.get_last_result
752
- @connection.send_query_prepared(stmt_key, binds.map { |col, val|
753
- type_cast(val, col)
754
- })
755
- @connection.block
756
- @connection.get_last_result
757
- rescue PGError => e
524
+ def exec_cache(sql, name, binds)
525
+ stmt_key = prepare_statement(sql)
526
+ type_casted_binds = binds.map { |col, val|
527
+ [col, type_cast(val, col)]
528
+ }
529
+
530
+ log(sql, name, type_casted_binds, stmt_key) do
531
+ @connection.exec_prepared(stmt_key, type_casted_binds.map { |_, val| val })
532
+ end
533
+ rescue ActiveRecord::StatementInvalid => e
534
+ pgerror = e.original_exception
535
+
758
536
  # Get the PG code for the failure. Annoyingly, the code for
759
537
  # prepared statements whose return value may have changed is
760
538
  # FEATURE_NOT_SUPPORTED. Check here for more details:
761
539
  # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
762
- code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
540
+ begin
541
+ code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
542
+ rescue
543
+ raise e
544
+ end
763
545
  if FEATURE_NOT_SUPPORTED == code
764
546
  @statements.delete sql_key(sql)
765
547
  retry
@@ -780,28 +562,30 @@ module ActiveRecord
780
562
  sql_key = sql_key(sql)
781
563
  unless @statements.key? sql_key
782
564
  nextkey = @statements.next_key
783
- @connection.prepare nextkey, sql
565
+ begin
566
+ @connection.prepare nextkey, sql
567
+ rescue => e
568
+ raise translate_exception_class(e, sql)
569
+ end
570
+ # Clear the queue
571
+ @connection.get_last_result
784
572
  @statements[sql_key] = nextkey
785
573
  end
786
574
  @statements[sql_key]
787
575
  end
788
576
 
789
- # The internal PostgreSQL identifier of the money data type.
790
- MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
791
- # The internal PostgreSQL identifier of the BYTEA data type.
792
- BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
793
-
794
577
  # Connects to a PostgreSQL server and sets up the adapter depending on the
795
578
  # connected server's characteristics.
796
579
  def connect
797
580
  @connection = PGconn.connect(@connection_parameters)
798
581
 
799
- # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
800
- # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
801
- # should know about this but can't detect it there, so deal with it here.
802
- RedshiftColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10
803
-
804
582
  configure_connection
583
+ rescue ::PG::Error => error
584
+ if error.message.include?("does not exist")
585
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
586
+ else
587
+ raise
588
+ end
805
589
  end
806
590
 
807
591
  # Configures the encoding, verbosity, schema search path, and time zone of the connection.
@@ -818,9 +602,9 @@ module ActiveRecord
818
602
  variables.map do |k, v|
819
603
  if v == ':default' || v == :default
820
604
  # Sets the value to the global or compile default
821
- execute("SET SESSION #{k.to_s} TO DEFAULT", 'SCHEMA')
605
+ execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA')
822
606
  elsif !v.nil?
823
- execute("SET SESSION #{k.to_s} TO #{quote(v)}", 'SCHEMA')
607
+ execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA')
824
608
  end
825
609
  end
826
610
  end
@@ -838,20 +622,6 @@ module ActiveRecord
838
622
  exec_query("SELECT currval('#{sequence_name}')", 'SQL')
839
623
  end
840
624
 
841
- # Executes a SELECT query and returns the results, performing any data type
842
- # conversions that are required to be performed here instead of in PostgreSQLColumn.
843
- def select(sql, name = nil, binds = [])
844
- exec_query(sql, name, binds)
845
- end
846
-
847
- def select_raw(sql, name = nil)
848
- res = execute(sql, name)
849
- results = result_as_array(res)
850
- fields = res.fields
851
- res.clear
852
- return fields, results
853
- end
854
-
855
625
  # Returns the list of a table's column names, data types, and default values.
856
626
  #
857
627
  # The underlying query is roughly:
@@ -870,7 +640,7 @@ module ActiveRecord
870
640
  # Query implementation notes:
871
641
  # - format_type includes the column size constraint, e.g. varchar(50)
872
642
  # - ::regclass is a function that gives the id for a table name
873
- def column_definitions(table_name) #:nodoc:
643
+ def column_definitions(table_name) # :nodoc:
874
644
  exec_query(<<-end_sql, 'SCHEMA').rows
875
645
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
876
646
  pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
@@ -882,27 +652,13 @@ module ActiveRecord
882
652
  end_sql
883
653
  end
884
654
 
885
- def extract_pg_identifier_from_name(name)
886
- match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
887
-
888
- if match_data
889
- rest = name[match_data[0].length, name.length]
890
- rest = rest[1, rest.length] if rest.start_with? "."
891
- [match_data[1], (rest.length > 0 ? rest : nil)]
892
- end
893
- end
894
-
895
- def extract_table_ref_from_insert_sql(sql)
896
- sql[/into\s+([^\(]*).*values\s*\(/i]
655
+ def extract_table_ref_from_insert_sql(sql) # :nodoc:
656
+ sql[/into\s+([^\(]*).*values\s*\(/im]
897
657
  $1.strip if $1
898
658
  end
899
659
 
900
- def create_table_definition(name, temporary, options, as = nil)
901
- TableDefinition.new native_database_types, name, temporary, options
902
- end
903
-
904
- def update_table_definition(table_name, base)
905
- Table.new(table_name, base)
660
+ def create_table_definition(name, temporary, options, as = nil) # :nodoc:
661
+ Redshift::TableDefinition.new native_database_types, name, temporary, options, as
906
662
  end
907
663
  end
908
664
  end