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
@@ -14,6 +14,10 @@ module ActiveRecord
14
14
  end
15
15
 
16
16
  def add_column_options!(sql, options)
17
+ if options[:array] || options[:column].try(:array)
18
+ sql << '[]'
19
+ end
20
+
17
21
  column = options.fetch(:column) { return super }
18
22
  if column.type == :uuid && options[:default] =~ /\(\)/
19
23
  sql << " DEFAULT #{options[:default]}"
@@ -39,7 +43,7 @@ module ActiveRecord
39
43
  create_database(name, options)
40
44
  end
41
45
 
42
- # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
46
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>,
43
47
  # <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
44
48
  # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
45
49
  # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
@@ -54,6 +58,16 @@ module ActiveRecord
54
58
  memo += case key
55
59
  when :owner
56
60
  " OWNER = \"#{value}\""
61
+ when :encoding
62
+ " ENCODING = '#{value}'"
63
+ when :collation
64
+ " LC_COLLATE = '#{value}'"
65
+ when :ctype
66
+ " LC_CTYPE = '#{value}'"
67
+ when :tablespace
68
+ " TABLESPACE = \"#{value}\""
69
+ when :connection_limit
70
+ " CONNECTION LIMIT = #{value}"
57
71
  else
58
72
  ""
59
73
  end
@@ -72,7 +86,7 @@ module ActiveRecord
72
86
 
73
87
  # Returns the list of all tables in the schema search path or a specified schema.
74
88
  def tables(name = nil)
75
- query(<<-SQL, 'SCHEMA').map { |row| row[0] }
89
+ query(<<-SQL, 'SCHEMA').map { |row| "#{row[0]}.#{row[1]}" }
76
90
  SELECT tablename
77
91
  FROM pg_tables
78
92
  WHERE schemaname = ANY (current_schemas(false))
@@ -110,7 +124,16 @@ module ActiveRecord
110
124
  end
111
125
 
112
126
  def index_name_exists?(table_name, index_name, default)
113
- false
127
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
128
+ SELECT COUNT(*)
129
+ FROM pg_class t
130
+ INNER JOIN pg_index d ON t.oid = d.indrelid
131
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
132
+ WHERE i.relkind = 'i'
133
+ AND i.relname = '#{index_name}'
134
+ AND t.relname = '#{table_name}'
135
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
136
+ SQL
114
137
  end
115
138
 
116
139
  # Returns an array of indexes for the given table.
@@ -151,10 +174,18 @@ module ActiveRecord
151
174
  end_sql
152
175
  end
153
176
 
177
+ # Returns the current database collation.
154
178
  def collation
179
+ query(<<-end_sql, 'SCHEMA')[0][0]
180
+ SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
181
+ end_sql
155
182
  end
156
183
 
184
+ # Returns the current database ctype.
157
185
  def ctype
186
+ query(<<-end_sql, 'SCHEMA')[0][0]
187
+ SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
188
+ end_sql
158
189
  end
159
190
 
160
191
  # Returns an array of schema names.
@@ -195,6 +226,17 @@ module ActiveRecord
195
226
  @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
196
227
  end
197
228
 
229
+ # Returns the current client message level.
230
+ def client_min_messages
231
+ query('SHOW client_min_messages', 'SCHEMA')[0][0]
232
+ end
233
+
234
+ # Set the client message level.
235
+ def client_min_messages=(level)
236
+ # can't set client_min_messages in redshift
237
+ # execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
238
+ end
239
+
198
240
  # Returns the sequence name for a table's primary key or some other specified key.
199
241
  def default_sequence_name(table_name, pk = nil) #:nodoc:
200
242
  result = serial_sequence(table_name, pk || 'id')
@@ -211,26 +253,113 @@ module ActiveRecord
211
253
  result.rows.first.first
212
254
  end
213
255
 
256
+ # Sets the sequence of a table's primary key to the specified value.
214
257
  def set_pk_sequence!(table, value) #:nodoc:
258
+ pk, sequence = pk_and_sequence_for(table)
259
+
260
+ if pk
261
+ if sequence
262
+ quoted_sequence = quote_table_name(sequence)
263
+
264
+ select_value <<-end_sql, 'SCHEMA'
265
+ SELECT setval('#{quoted_sequence}', #{value})
266
+ end_sql
267
+ else
268
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
269
+ end
270
+ end
215
271
  end
216
272
 
273
+ # Resets the sequence of a table's primary key to the maximum value.
217
274
  def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
275
+ unless pk and sequence
276
+ default_pk, default_sequence = pk_and_sequence_for(table)
277
+
278
+ pk ||= default_pk
279
+ sequence ||= default_sequence
280
+ end
281
+
282
+ if @logger && pk && !sequence
283
+ @logger.warn "#{table} has primary key #{pk} with no default sequence"
284
+ end
285
+
286
+ if pk && sequence
287
+ quoted_sequence = quote_table_name(sequence)
288
+
289
+ select_value <<-end_sql, 'SCHEMA'
290
+ SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
291
+ end_sql
292
+ end
218
293
  end
219
294
 
295
+ # Returns a table's primary key and belonging sequence.
220
296
  def pk_and_sequence_for(table) #:nodoc:
221
- [nil, nil]
297
+ # First try looking for a sequence with a dependency on the
298
+ # given table's primary key.
299
+ result = query(<<-end_sql, 'SCHEMA')[0]
300
+ SELECT attr.attname, nsp.nspname, seq.relname
301
+ FROM pg_class seq,
302
+ pg_attribute attr,
303
+ pg_depend dep,
304
+ pg_constraint cons,
305
+ pg_namespace nsp
306
+ WHERE seq.oid = dep.objid
307
+ AND seq.relkind = 'S'
308
+ AND attr.attrelid = dep.refobjid
309
+ AND attr.attnum = dep.refobjsubid
310
+ AND attr.attrelid = cons.conrelid
311
+ AND attr.attnum = cons.conkey[1]
312
+ AND seq.relnamespace = nsp.oid
313
+ AND cons.contype = 'p'
314
+ AND dep.classid = 'pg_class'::regclass
315
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
316
+ end_sql
317
+
318
+ if result.nil? or result.empty?
319
+ result = query(<<-end_sql, 'SCHEMA')[0]
320
+ SELECT attr.attname, nsp.nspname,
321
+ CASE
322
+ WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
323
+ WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
324
+ substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
325
+ strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
326
+ ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
327
+ END
328
+ FROM pg_class t
329
+ JOIN pg_attribute attr ON (t.oid = attrelid)
330
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
331
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
332
+ JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
333
+ WHERE t.oid = '#{quote_table_name(table)}'::regclass
334
+ AND cons.contype = 'p'
335
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
336
+ end_sql
337
+ end
338
+
339
+ pk = result.shift
340
+ if result.last
341
+ [pk, Redshift::Name.new(*result)]
342
+ else
343
+ [pk, nil]
344
+ end
345
+ rescue
346
+ nil
222
347
  end
223
348
 
224
349
  # Returns just a table's primary key
225
350
  def primary_key(table)
351
+ # Passing the env variable REDSHIFT_SCHEMA will allow the correct generation of id
352
+ # fields that are not of integer/uuid/serial type
353
+ return if ENV['REDSHIFT_SCHEMA']
226
354
  pks = exec_query(<<-end_sql, 'SCHEMA').rows
227
355
  SELECT DISTINCT attr.attname
228
356
  FROM pg_attribute attr
229
357
  INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
230
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
358
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
231
359
  WHERE cons.contype = 'p'
232
360
  AND dep.refobjid = '#{quote_table_name(table)}'::regclass
233
361
  end_sql
362
+
234
363
  return nil unless pks.count == 1
235
364
  pks[0][0]
236
365
  end
@@ -244,9 +373,21 @@ module ActiveRecord
244
373
  def rename_table(table_name, new_name)
245
374
  clear_cache!
246
375
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
376
+ pk, seq = pk_and_sequence_for(new_name)
377
+ if seq && seq.identifier == "#{table_name}_#{pk}_seq"
378
+ new_seq = "#{new_name}_#{pk}_seq"
379
+ idx = "#{table_name}_pkey"
380
+ new_idx = "#{new_name}_pkey"
381
+ execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
382
+ execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
383
+ end
384
+
385
+ rename_table_indexes(table_name, new_name)
247
386
  end
248
387
 
249
- def add_column(table_name, column_name, type, options = {}) #:nodoc:
388
+ # Adds a new column to the named table.
389
+ # See TableDefinition#column for details of the options you can use.
390
+ def add_column(table_name, column_name, type, options = {})
250
391
  clear_cache!
251
392
  super
252
393
  end
@@ -294,24 +435,30 @@ module ActiveRecord
294
435
  end
295
436
 
296
437
  # Renames a column in a table.
297
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
438
+ def rename_column(table_name, column_name, new_column_name)
298
439
  clear_cache!
299
440
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
300
441
  rename_column_indexes(table_name, column_name, new_column_name)
301
442
  end
302
443
 
303
444
  def add_index(table_name, column_name, options = {}) #:nodoc:
445
+ # ignore
304
446
  end
305
447
 
306
448
  def remove_index!(table_name, index_name) #:nodoc:
449
+ # ignore
307
450
  end
308
451
 
309
452
  def rename_index(table_name, old_name, new_name)
453
+ validate_index_length!(table_name, new_name)
454
+
455
+ execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
456
+ # ignore
310
457
  end
311
458
 
312
459
  def foreign_keys(table_name)
313
460
  fk_info = select_all <<-SQL.strip_heredoc
314
- SELECT t2.relname AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
461
+ SELECT t2.oid::regclass AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
315
462
  FROM pg_constraint c
316
463
  JOIN pg_class t1 ON c.conrelid = t1.oid
317
464
  JOIN pg_class t2 ON c.confrelid = t2.oid
@@ -353,6 +500,22 @@ module ActiveRecord
353
500
  # Maps logical Rails types to PostgreSQL-specific data types.
354
501
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
355
502
  case type.to_s
503
+ when 'boolean'
504
+ super(type)
505
+ when 'binary'
506
+ # PostgreSQL doesn't support limits on binary (bytea) columns.
507
+ # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
508
+ case limit
509
+ when nil, 0..0x3fffffff; super(type)
510
+ else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
511
+ end
512
+ when 'text'
513
+ # PostgreSQL doesn't support limits on text columns.
514
+ # The hard limit is 1Gb, according to section 8.3 in the manual.
515
+ case limit
516
+ when nil, 0..0x3fffffff; super(type)
517
+ else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
518
+ end
356
519
  when 'integer'
357
520
  return 'integer' unless limit
358
521
 
@@ -18,11 +18,7 @@ module ActiveRecord
18
18
  end
19
19
 
20
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
21
+ parts.map { |p| PGconn.quote_ident(p) }.join SEPARATOR
26
22
  end
27
23
 
28
24
  def ==(o)
@@ -36,11 +32,8 @@ module ActiveRecord
36
32
 
37
33
  protected
38
34
  def unquote(part)
39
- if part && part.start_with?('"')
40
- part[1..-2]
41
- else
42
- part
43
- end
35
+ return unless part
36
+ part.gsub(/(^"|"$)/,'')
44
37
  end
45
38
 
46
39
  def parts
@@ -64,11 +57,7 @@ module ActiveRecord
64
57
  # * <tt>"schema_name".table_name</tt>
65
58
  # * <tt>"schema.name"."table name"</tt>
66
59
  def extract_schema_qualified_name(string)
67
- schema, table = string.scan(/[^".\s]+|"[^"]*"/)
68
- if table.nil?
69
- table = schema
70
- schema = nil
71
- end
60
+ table, schema = string.scan(/[^".\s]+|"[^"]*"/)[0..1].reverse
72
61
  Redshift::Name.new(schema, table)
73
62
  end
74
63
  end
@@ -13,14 +13,14 @@ require 'active_record/connection_adapters/redshift/database_statements'
13
13
  require 'arel/visitors/bind_visitor'
14
14
 
15
15
  # Make sure we're using pg high enough for PGResult#values
16
- gem 'pg', '> 0.15'
16
+ gem 'pg', '~> 0.15'
17
17
  require 'pg'
18
18
 
19
19
  require 'ipaddr'
20
20
 
21
21
  module ActiveRecord
22
22
  module ConnectionHandling # :nodoc:
23
- RS_VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
23
+ VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
24
24
  :client_encoding, :options, :application_name, :fallback_application_name,
25
25
  :keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
26
26
  :tty, :sslmode, :requiressl, :sslcompression, :sslcert, :sslkey,
@@ -37,7 +37,7 @@ module ActiveRecord
37
37
  conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
38
38
 
39
39
  # Forward only valid config params to PGconn.connect.
40
- conn_params.keep_if { |k, _| RS_VALID_CONN_PARAMS.include?(k) }
40
+ conn_params.keep_if { |k, _| VALID_CONN_PARAMS.include?(k) }
41
41
 
42
42
  # The postgres drivers don't allow the creation of an unconnected PGconn object,
43
43
  # so just pass a nil connection object for the time being.
@@ -64,7 +64,8 @@ module ActiveRecord
64
64
  # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
65
65
  # * <tt>:variables</tt> - An optional hash of additional parameters that
66
66
  # will be used in <tt>SET SESSION key = val</tt> calls on the connection.
67
- # * <tt>:insert_returning</tt> - Does nothing for Redshift.
67
+ # * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT</tt> statements
68
+ # defaults to true.
68
69
  #
69
70
  # Any further options are used as connection parameters to libpq. See
70
71
  # http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
@@ -76,17 +77,40 @@ module ActiveRecord
76
77
  ADAPTER_NAME = 'Redshift'.freeze
77
78
 
78
79
  NATIVE_DATABASE_TYPES = {
79
- primary_key: "integer identity primary key",
80
- string: { name: "varchar" },
81
- text: { name: "varchar" },
80
+ primary_key: "integer identity",
81
+ bigserial: { name: "bigint" },
82
+ string: { name: "character varying" },
83
+ text: { name: "text" },
82
84
  integer: { name: "integer" },
83
85
  float: { name: "float" },
84
86
  decimal: { name: "decimal" },
85
87
  datetime: { name: "timestamp" },
86
88
  time: { name: "time" },
87
89
  date: { name: "date" },
88
- bigint: { name: "bigint" },
90
+ daterange: { name: "daterange" },
91
+ numrange: { name: "numrange" },
92
+ tsrange: { name: "tsrange" },
93
+ tstzrange: { name: "tstzrange" },
94
+ int4range: { name: "int4range" },
95
+ int8range: { name: "int8range" },
96
+ binary: { name: "bytea" },
89
97
  boolean: { name: "boolean" },
98
+ bigint: { name: "bigint" },
99
+ xml: { name: "xml" },
100
+ tsvector: { name: "tsvector" },
101
+ hstore: { name: "hstore" },
102
+ inet: { name: "inet" },
103
+ cidr: { name: "cidr" },
104
+ macaddr: { name: "macaddr" },
105
+ uuid: { name: "uuid" },
106
+ json: { name: "json" },
107
+ jsonb: { name: "jsonb" },
108
+ ltree: { name: "ltree" },
109
+ citext: { name: "citext" },
110
+ point: { name: "point" },
111
+ bit: { name: "bit" },
112
+ bit_varying: { name: "bit varying" },
113
+ money: { name: "money" },
90
114
  }
91
115
 
92
116
  OID = Redshift::OID #:nodoc:
@@ -95,6 +119,7 @@ module ActiveRecord
95
119
  include Redshift::ReferentialIntegrity
96
120
  include Redshift::SchemaStatements
97
121
  include Redshift::DatabaseStatements
122
+ include Savepoints
98
123
 
99
124
  def schema_creation # :nodoc:
100
125
  Redshift::SchemaCreation.new self
@@ -104,10 +129,16 @@ module ActiveRecord
104
129
  # AbstractAdapter
105
130
  def prepare_column_options(column, types) # :nodoc:
106
131
  spec = super
132
+ spec[:array] = 'true' if column.respond_to?(:array) && column.array
107
133
  spec[:default] = "\"#{column.default_function}\"" if column.default_function
108
134
  spec
109
135
  end
110
136
 
137
+ # Adds +:array+ as a valid migration key
138
+ def migration_keys
139
+ super + [:array]
140
+ end
141
+
111
142
  # Returns +true+, since this connection adapter supports prepared statement
112
143
  # caching.
113
144
  def supports_statement_cache?
@@ -115,15 +146,15 @@ module ActiveRecord
115
146
  end
116
147
 
117
148
  def supports_index_sort_order?
118
- false
149
+ true
119
150
  end
120
151
 
121
152
  def supports_partial_index?
122
- false
153
+ true
123
154
  end
124
155
 
125
156
  def supports_transaction_isolation?
126
- false
157
+ true
127
158
  end
128
159
 
129
160
  def supports_foreign_keys?
@@ -196,8 +227,13 @@ module ActiveRecord
196
227
  super(connection, logger)
197
228
 
198
229
  @visitor = Arel::Visitors::PostgreSQL.new self
199
- @prepared_statements = false
230
+ if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
231
+ @prepared_statements = true
232
+ else
233
+ @prepared_statements = false
234
+ end
200
235
 
236
+ connection_parameters.delete :prepared_statements
201
237
  @connection_parameters, @config = connection_parameters, config
202
238
 
203
239
  # @local_tz is initialized as nil to avoid warnings when connect tries to use it
@@ -269,14 +305,6 @@ module ActiveRecord
269
305
  true
270
306
  end
271
307
 
272
- # Enable standard-conforming strings if available.
273
- def set_standard_conforming_strings
274
- old, self.client_min_messages = client_min_messages, 'panic'
275
- execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
276
- ensure
277
- self.client_min_messages = old
278
- end
279
-
280
308
  def supports_ddl_transactions?
281
309
  true
282
310
  end
@@ -285,30 +313,46 @@ module ActiveRecord
285
313
  true
286
314
  end
287
315
 
316
+ # Returns true if pg > 9.1
288
317
  def supports_extensions?
289
- false
318
+ redshift_version >= 90100
290
319
  end
291
320
 
321
+ # Range datatypes weren't introduced until PostgreSQL 9.2
292
322
  def supports_ranges?
293
- false
323
+ redshift_version >= 90200
294
324
  end
295
325
 
296
326
  def supports_materialized_views?
297
- false
298
- end
299
-
300
- def supports_import?
301
- true
327
+ redshift_version >= 90300
302
328
  end
303
329
 
304
330
  def enable_extension(name)
331
+ exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
332
+ reload_type_map
333
+ }
305
334
  end
306
335
 
307
336
  def disable_extension(name)
337
+ exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
338
+ reload_type_map
339
+ }
308
340
  end
309
341
 
310
342
  def extension_enabled?(name)
311
- false
343
+ if supports_extensions?
344
+ res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled",
345
+ 'SCHEMA'
346
+ res.cast_values.first
347
+ end
348
+ end
349
+
350
+ def extensions
351
+ if supports_extensions?
352
+ exec_query("SELECT extname from pg_extension", "SCHEMA").cast_values
353
+ else
354
+ super
355
+ end
312
356
  end
313
357
 
314
358
  # Returns the configured supported identifier length supported by PostgreSQL
@@ -356,13 +400,17 @@ module ActiveRecord
356
400
  @connection.server_version
357
401
  end
358
402
 
403
+ # See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
404
+ FOREIGN_KEY_VIOLATION = "23503"
405
+ UNIQUE_VIOLATION = "23505"
406
+
359
407
  def translate_exception(exception, message)
360
408
  return exception unless exception.respond_to?(:result)
361
409
 
362
- case exception.message
363
- when /duplicate key value violates unique constraint/
410
+ case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
411
+ when UNIQUE_VIOLATION
364
412
  RecordNotUnique.new(message, exception)
365
- when /violates foreign key constraint/
413
+ when FOREIGN_KEY_VIOLATION
366
414
  InvalidForeignKey.new(message, exception)
367
415
  else
368
416
  super
@@ -377,7 +425,11 @@ module ActiveRecord
377
425
  end
378
426
 
379
427
  type_map.fetch(oid, fmod, sql_type) {
380
- warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
428
+ # oid=2205 maps to t2.oid::regclass, which seems to do fine as string
429
+ if oid != 2205
430
+ warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
431
+ end
432
+
381
433
  Type::Value.new.tap do |cast_type|
382
434
  type_map.register_type(oid, cast_type)
383
435
  end
@@ -397,10 +449,36 @@ module ActiveRecord
397
449
  m.alias_type 'name', 'varchar'
398
450
  m.alias_type 'bpchar', 'varchar'
399
451
  m.register_type 'bool', Type::Boolean.new
452
+ register_class_with_limit m, 'bit', OID::Bit
453
+ register_class_with_limit m, 'varbit', OID::BitVarying
400
454
  m.alias_type 'timestamptz', 'timestamp'
401
455
  m.register_type 'date', OID::Date.new
402
456
  m.register_type 'time', OID::Time.new
403
457
 
458
+ m.register_type 'money', OID::Money.new
459
+ m.register_type 'bytea', OID::Bytea.new
460
+ m.register_type 'point', OID::Point.new
461
+ m.register_type 'hstore', OID::Hstore.new
462
+ m.register_type 'json', OID::Json.new
463
+ m.register_type 'jsonb', OID::Jsonb.new
464
+ m.register_type 'cidr', OID::Cidr.new
465
+ m.register_type 'inet', OID::Inet.new
466
+ m.register_type 'uuid', OID::Uuid.new
467
+ m.register_type 'xml', OID::Xml.new
468
+ m.register_type 'tsvector', OID::SpecializedString.new(:tsvector)
469
+ m.register_type 'macaddr', OID::SpecializedString.new(:macaddr)
470
+ m.register_type 'citext', OID::SpecializedString.new(:citext)
471
+ m.register_type 'ltree', OID::SpecializedString.new(:ltree)
472
+
473
+ # FIXME: why are we keeping these types as strings?
474
+ m.alias_type 'interval', 'varchar'
475
+ m.alias_type 'path', 'varchar'
476
+ m.alias_type 'line', 'varchar'
477
+ m.alias_type 'polygon', 'varchar'
478
+ m.alias_type 'circle', 'varchar'
479
+ m.alias_type 'lseg', 'varchar'
480
+ m.alias_type 'box', 'varchar'
481
+
404
482
  m.register_type 'timestamp' do |_, _, sql_type|
405
483
  precision = extract_precision(sql_type)
406
484
  OID::DateTime.new(precision: precision)
@@ -450,7 +528,7 @@ module ActiveRecord
450
528
  when 'true', 'false'
451
529
  default
452
530
  # Numeric types
453
- when /\A\(?(-?\d+(\.\d*)?)\)?\z/
531
+ when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
454
532
  $1
455
533
  # Object identifier types
456
534
  when /\A-?\d+\z/
@@ -471,8 +549,6 @@ module ActiveRecord
471
549
  end
472
550
 
473
551
  def load_additional_types(type_map, oids = nil) # :nodoc:
474
- initializer = OID::TypeMapInitializer.new(type_map)
475
-
476
552
  if supports_ranges?
477
553
  query = <<-SQL
478
554
  SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
@@ -490,9 +566,9 @@ module ActiveRecord
490
566
  query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
491
567
  end
492
568
 
493
- execute_and_clear(query, 'SCHEMA', []) do |records|
494
- initializer.run(records)
495
- end
569
+ initializer = OID::TypeMapInitializer.new(type_map)
570
+ records = execute(query, 'SCHEMA')
571
+ initializer.run(records)
496
572
  end
497
573
 
498
574
  FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
@@ -567,6 +643,11 @@ module ActiveRecord
567
643
  def connect
568
644
  @connection = PGconn.connect(@connection_parameters)
569
645
 
646
+ # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
647
+ # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
648
+ # should know about this but can't detect it there, so deal with it here.
649
+ OID::Money.precision = (redshift_version >= 80300) ? 19 : 10
650
+
570
651
  configure_connection
571
652
  rescue ::PG::Error => error
572
653
  if error.message.include?("does not exist")