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
@@ -1,28 +1,13 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
- class RedshiftAdapter < AbstractAdapter
4
- module ReferentialIntegrity
5
- def supports_disable_referential_integrity? #:nodoc:
3
+ module Redshift
4
+ module ReferentialIntegrity # :nodoc:
5
+ def supports_disable_referential_integrity? # :nodoc:
6
6
  true
7
7
  end
8
8
 
9
- def disable_referential_integrity #:nodoc:
10
- if supports_disable_referential_integrity?
11
- begin
12
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
13
- rescue
14
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER USER" }.join(";"))
15
- end
16
- end
9
+ def disable_referential_integrity # :nodoc:
17
10
  yield
18
- ensure
19
- if supports_disable_referential_integrity?
20
- begin
21
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
22
- rescue
23
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER USER" }.join(";"))
24
- end
25
- end
26
11
  end
27
12
  end
28
13
  end
@@ -0,0 +1,73 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Redshift
4
+ module ColumnMethods
5
+ def json(name, options = {})
6
+ column(name, :json, options)
7
+ end
8
+
9
+ def jsonb(name, options = {})
10
+ column(name, :jsonb, options)
11
+ end
12
+ end
13
+
14
+ class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
15
+ end
16
+
17
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
18
+ include ColumnMethods
19
+
20
+ # Defines the primary key field.
21
+ # Use of the native PostgreSQL UUID type is supported, and can be used
22
+ # by defining your tables as such:
23
+ #
24
+ # create_table :stuffs, id: :uuid do |t|
25
+ # t.string :content
26
+ # t.timestamps
27
+ # end
28
+ #
29
+ # By default, this will use the +uuid_generate_v4()+ function from the
30
+ # +uuid-ossp+ extension, which MUST be enabled on your database. To enable
31
+ # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
32
+ # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can
33
+ # set the +:default+ option to +nil+:
34
+ #
35
+ # create_table :stuffs, id: false do |t|
36
+ # t.primary_key :id, :uuid, default: nil
37
+ # t.uuid :foo_id
38
+ # t.timestamps
39
+ # end
40
+ #
41
+ # You may also pass a different UUID generation function from +uuid-ossp+
42
+ # or another library.
43
+ #
44
+ # Note that setting the UUID primary key default value to +nil+ will
45
+ # require you to assure that you always provide a UUID value before saving
46
+ # a record (as primary keys cannot be +nil+). This might be done via the
47
+ # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
48
+ def primary_key(name, type = :primary_key, options = {})
49
+ return super unless type == :uuid
50
+ options[:default] = options.fetch(:default, 'uuid_generate_v4()')
51
+ options[:primary_key] = true
52
+ column name, type, options
53
+ end
54
+
55
+ def new_column_definition(name, type, options) # :nodoc:
56
+ column = super
57
+ column.array = options[:array]
58
+ column
59
+ end
60
+
61
+ private
62
+
63
+ def create_column_definition(name, type)
64
+ Redshift::ColumnDefinition.new name, type
65
+ end
66
+ end
67
+
68
+ class Table < ActiveRecord::ConnectionAdapters::Table
69
+ include ColumnMethods
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,18 +1,12 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
- class RedshiftAdapter < AbstractAdapter
3
+ module Redshift
4
4
  class SchemaCreation < AbstractAdapter::SchemaCreation
5
5
  private
6
6
 
7
- def visit_AddColumn(o)
8
- sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
9
- sql = "ADD COLUMN #{quote_column_name(o.name)} #{sql_type}"
10
- add_column_options!(sql, column_options(o))
11
- end
12
-
13
7
  def visit_ColumnDefinition(o)
14
8
  sql = super
15
- if o.primary_key? && o.type == :uuid
9
+ if o.primary_key? && o.type != :primary_key
16
10
  sql << " PRIMARY KEY "
17
11
  add_column_options!(sql, column_options(o))
18
12
  end
@@ -31,10 +25,14 @@ module ActiveRecord
31
25
  super
32
26
  end
33
27
  end
34
- end
35
28
 
36
- def schema_creation
37
- SchemaCreation.new self
29
+ def type_for_column(column)
30
+ if column.array
31
+ @conn.lookup_cast_type("#{column.sql_type}[]")
32
+ else
33
+ super
34
+ end
35
+ end
38
36
  end
39
37
 
40
38
  module SchemaStatements
@@ -46,7 +44,7 @@ module ActiveRecord
46
44
  end
47
45
 
48
46
  # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
49
- # <tt>:encoding</tt>, <tt>:collation</tt>, <tt>:ctype</tt>,
47
+ # <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
50
48
  # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
51
49
  # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
52
50
  #
@@ -56,8 +54,8 @@ module ActiveRecord
56
54
  def create_database(name, options = {})
57
55
  options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
58
56
 
59
- option_string = options.sum do |key, value|
60
- case key
57
+ option_string = options.inject("") do |memo, (key, value)|
58
+ memo += case key
61
59
  when :owner
62
60
  " OWNER = \"#{value}\""
63
61
  when :template
@@ -90,7 +88,7 @@ module ActiveRecord
90
88
 
91
89
  # Returns the list of all tables in the schema search path or a specified schema.
92
90
  def tables(name = nil)
93
- query(<<-SQL, 'SCHEMA').map { |row| "#{row[0]}.#{row[1]}" }
91
+ query(<<-SQL, 'SCHEMA').map { |row| row[0] }
94
92
  SELECT tablename
95
93
  FROM pg_tables
96
94
  WHERE schemaname = ANY (current_schemas(false))
@@ -101,22 +99,23 @@ module ActiveRecord
101
99
  # If the schema is not specified as part of +name+ then it will only find tables within
102
100
  # the current schema search path (regardless of permissions to access tables in other schemas)
103
101
  def table_exists?(name)
104
- schema, table = Utils.extract_schema_and_table(name.to_s)
105
- return false unless table
106
-
107
- binds = [[nil, table]]
108
- binds << [nil, schema] if schema
102
+ name = Utils.extract_schema_qualified_name(name.to_s)
103
+ return false unless name.identifier
109
104
 
110
105
  exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
111
106
  SELECT COUNT(*)
112
107
  FROM pg_class c
113
108
  LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
114
- WHERE c.relkind in ('v','r')
115
- AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
116
- AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
109
+ WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
110
+ AND c.relname = '#{name.identifier}'
111
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
117
112
  SQL
118
113
  end
119
114
 
115
+ def drop_table(table_name, options = {})
116
+ execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
117
+ end
118
+
120
119
  # Returns true if schema exists.
121
120
  def schema_exists?(name)
122
121
  exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
@@ -126,22 +125,30 @@ module ActiveRecord
126
125
  SQL
127
126
  end
128
127
 
128
+ def index_name_exists?(table_name, index_name, default)
129
+ false
130
+ end
131
+
129
132
  # Returns an array of indexes for the given table.
130
133
  def indexes(table_name, name = nil)
131
- []
134
+ []
132
135
  end
133
136
 
134
137
  # Returns the list of all column definitions for a table.
135
138
  def columns(table_name)
136
139
  # Limit, precision, and scale are all handled by the superclass.
137
140
  column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
138
- oid = OID::TYPE_MAP.fetch(oid.to_i, fmod.to_i) {
139
- OID::Identity.new
140
- }
141
- RedshiftColumn.new(column_name, default, oid, type, notnull == 'f')
141
+ oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type)
142
+ default_value = extract_value_from_default(oid, default)
143
+ default_function = extract_default_function(default_value, default)
144
+ new_column(column_name, default_value, oid, type, notnull == 'f', default_function)
142
145
  end
143
146
  end
144
147
 
148
+ def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil) # :nodoc:
149
+ RedshiftColumn.new(name, default, cast_type, sql_type, null, default_function)
150
+ end
151
+
145
152
  # Returns the current database name.
146
153
  def current_database
147
154
  query('select current_database()', 'SCHEMA')[0][0]
@@ -216,9 +223,9 @@ module ActiveRecord
216
223
  def default_sequence_name(table_name, pk = nil) #:nodoc:
217
224
  result = serial_sequence(table_name, pk || 'id')
218
225
  return nil unless result
219
- result.split('.').last
226
+ Utils.extract_schema_qualified_name(result).to_s
220
227
  rescue ActiveRecord::StatementInvalid
221
- "#{table_name}_#{pk || 'id'}_seq"
228
+ Redshift::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
222
229
  end
223
230
 
224
231
  def serial_sequence(table, column)
@@ -228,6 +235,23 @@ module ActiveRecord
228
235
  result.rows.first.first
229
236
  end
230
237
 
238
+ # Sets the sequence of a table's primary key to the specified value.
239
+ def set_pk_sequence!(table, value) #:nodoc:
240
+ pk, sequence = pk_and_sequence_for(table)
241
+
242
+ if pk
243
+ if sequence
244
+ quoted_sequence = quote_table_name(sequence)
245
+
246
+ select_value <<-end_sql, 'SCHEMA'
247
+ SELECT setval('#{quoted_sequence}', #{value})
248
+ end_sql
249
+ else
250
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
251
+ end
252
+ end
253
+ end
254
+
231
255
  # Resets the sequence of a table's primary key to the maximum value.
232
256
  def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
233
257
  unless pk and sequence
@@ -255,25 +279,29 @@ module ActiveRecord
255
279
  # First try looking for a sequence with a dependency on the
256
280
  # given table's primary key.
257
281
  result = query(<<-end_sql, 'SCHEMA')[0]
258
- SELECT attr.attname, seq.relname
282
+ SELECT attr.attname, nsp.nspname, seq.relname
259
283
  FROM pg_class seq,
260
284
  pg_attribute attr,
261
285
  pg_depend dep,
262
- pg_constraint cons
286
+ pg_constraint cons,
287
+ pg_namespace nsp
263
288
  WHERE seq.oid = dep.objid
264
289
  AND seq.relkind = 'S'
265
290
  AND attr.attrelid = dep.refobjid
266
291
  AND attr.attnum = dep.refobjsubid
267
292
  AND attr.attrelid = cons.conrelid
268
293
  AND attr.attnum = cons.conkey[1]
294
+ AND seq.relnamespace = nsp.oid
269
295
  AND cons.contype = 'p'
296
+ AND dep.classid = 'pg_class'::regclass
270
297
  AND dep.refobjid = '#{quote_table_name(table)}'::regclass
271
298
  end_sql
272
299
 
273
300
  if result.nil? or result.empty?
274
301
  result = query(<<-end_sql, 'SCHEMA')[0]
275
- SELECT attr.attname,
302
+ SELECT attr.attname, nsp.nspname,
276
303
  CASE
304
+ WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
277
305
  WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
278
306
  substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
279
307
  strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
@@ -283,52 +311,49 @@ module ActiveRecord
283
311
  JOIN pg_attribute attr ON (t.oid = attrelid)
284
312
  JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
285
313
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
314
+ JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
286
315
  WHERE t.oid = '#{quote_table_name(table)}'::regclass
287
316
  AND cons.contype = 'p'
288
- AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval'
317
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
289
318
  end_sql
290
319
  end
291
320
 
292
- [result.first, result.last]
321
+ pk = result.shift
322
+ if result.last
323
+ [pk, PostgreSQL::Name.new(*result)]
324
+ else
325
+ [pk, nil]
326
+ end
293
327
  rescue
294
328
  nil
295
329
  end
296
330
 
297
331
  # Returns just a table's primary key
298
332
  def primary_key(table)
299
- row = exec_query(<<-end_sql, 'SCHEMA').rows.first
333
+ pks = exec_query(<<-end_sql, 'SCHEMA').rows
300
334
  SELECT DISTINCT attr.attname
301
335
  FROM pg_attribute attr
302
336
  INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
303
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
337
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
304
338
  WHERE cons.contype = 'p'
305
339
  AND dep.refobjid = '#{quote_table_name(table)}'::regclass
306
340
  end_sql
307
-
308
- row && row.first
341
+ return nil unless pks.count == 1
342
+ pks[0][0]
309
343
  end
310
344
 
311
345
  # Renames a table.
312
- # Also renames a table's primary key sequence if the sequence name matches the
313
- # Active Record default.
346
+ # Also renames a table's primary key sequence if the sequence name exists and
347
+ # matches the Active Record default.
314
348
  #
315
349
  # Example:
316
350
  # rename_table('octopuses', 'octopi')
317
351
  def rename_table(table_name, new_name)
318
352
  clear_cache!
319
353
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
320
- pk, seq = pk_and_sequence_for(new_name)
321
- if seq == "#{table_name}_#{pk}_seq"
322
- new_seq = "#{new_name}_#{pk}_seq"
323
- execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
324
- end
325
-
326
- rename_table_indexes(table_name, new_name)
327
354
  end
328
355
 
329
- # Adds a new column to the named table.
330
- # See TableDefinition#column for details of the options you can use.
331
- def add_column(table_name, column_name, type, options = {})
356
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
332
357
  clear_cache!
333
358
  super
334
359
  end
@@ -337,8 +362,14 @@ module ActiveRecord
337
362
  def change_column(table_name, column_name, type, options = {})
338
363
  clear_cache!
339
364
  quoted_table_name = quote_table_name(table_name)
340
-
341
- execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
365
+ sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
366
+ sql_type << "[]" if options[:array]
367
+ sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
368
+ sql << " USING #{options[:using]}" if options[:using]
369
+ if options[:cast_as]
370
+ sql << " USING CAST(#{quote_column_name(column_name)} AS #{type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale])})"
371
+ end
372
+ execute sql
342
373
 
343
374
  change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
344
375
  change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
@@ -347,34 +378,79 @@ module ActiveRecord
347
378
  # Changes the default value of a table column.
348
379
  def change_column_default(table_name, column_name, default)
349
380
  clear_cache!
350
- execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
381
+ column = column_for(table_name, column_name)
382
+ return unless column
383
+
384
+ alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
385
+ if default.nil?
386
+ # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
387
+ # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
388
+ execute alter_column_query % "DROP DEFAULT"
389
+ else
390
+ execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}"
391
+ end
351
392
  end
352
393
 
353
394
  def change_column_null(table_name, column_name, null, default = nil)
354
395
  clear_cache!
355
396
  unless null || default.nil?
356
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
397
+ column = column_for(table_name, column_name)
398
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
357
399
  end
358
400
  execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
359
401
  end
360
402
 
361
403
  # Renames a column in a table.
362
- def rename_column(table_name, column_name, new_column_name)
404
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
363
405
  clear_cache!
364
406
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
365
407
  rename_column_indexes(table_name, column_name, new_column_name)
366
408
  end
367
409
 
368
- def add_index(*args)
369
- # ignore
410
+ def add_index(table_name, column_name, options = {}) #:nodoc:
370
411
  end
371
412
 
372
413
  def remove_index!(table_name, index_name) #:nodoc:
373
- # ignore
374
414
  end
375
415
 
376
416
  def rename_index(table_name, old_name, new_name)
377
- # ignore
417
+ end
418
+
419
+ def foreign_keys(table_name)
420
+ fk_info = select_all <<-SQL.strip_heredoc
421
+ SELECT t2.oid::regclass::text 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
422
+ FROM pg_constraint c
423
+ JOIN pg_class t1 ON c.conrelid = t1.oid
424
+ JOIN pg_class t2 ON c.confrelid = t2.oid
425
+ JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
426
+ JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
427
+ JOIN pg_namespace t3 ON c.connamespace = t3.oid
428
+ WHERE c.contype = 'f'
429
+ AND t1.relname = #{quote(table_name)}
430
+ AND t3.nspname = ANY (current_schemas(false))
431
+ ORDER BY c.conname
432
+ SQL
433
+
434
+ fk_info.map do |row|
435
+ options = {
436
+ column: row['column'],
437
+ name: row['name'],
438
+ primary_key: row['primary_key']
439
+ }
440
+
441
+ options[:on_delete] = extract_foreign_key_action(row['on_delete'])
442
+ options[:on_update] = extract_foreign_key_action(row['on_update'])
443
+
444
+ ForeignKeyDefinition.new(table_name, row['to_table'], options)
445
+ end
446
+ end
447
+
448
+ def extract_foreign_key_action(specifier) # :nodoc:
449
+ case specifier
450
+ when 'c'; :cascade
451
+ when 'n'; :nullify
452
+ when 'r'; :restrict
453
+ end
378
454
  end
379
455
 
380
456
  def index_name_length
@@ -384,13 +460,6 @@ module ActiveRecord
384
460
  # Maps logical Rails types to PostgreSQL-specific data types.
385
461
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
386
462
  case type.to_s
387
- when 'binary'
388
- # PostgreSQL doesn't support limits on binary (bytea) columns.
389
- # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
390
- case limit
391
- when nil, 0..0x3fffffff; super(type)
392
- else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
393
- end
394
463
  when 'integer'
395
464
  return 'integer' unless limit
396
465
 
@@ -412,22 +481,18 @@ module ActiveRecord
412
481
  end
413
482
  end
414
483
 
415
- # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
416
- #
417
484
  # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
418
485
  # requires that the ORDER BY include the distinct column.
419
- #
420
- # distinct("posts.id", ["posts.created_at desc"])
421
- # # => "DISTINCT posts.id, posts.created_at AS alias_0"
422
- def distinct(columns, orders) #:nodoc:
423
- order_columns = orders.map{ |s|
486
+ def columns_for_distinct(columns, orders) #:nodoc:
487
+ order_columns = orders.reject(&:blank?).map{ |s|
424
488
  # Convert Arel node to string
425
489
  s = s.to_sql unless s.is_a?(String)
426
490
  # Remove any ASC/DESC modifiers
427
- s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
491
+ s.gsub(/\s+(?:ASC|DESC)\b/i, '')
492
+ .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
428
493
  }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
429
494
 
430
- [super].concat(order_columns).join(', ')
495
+ [super, *order_columns].join(', ')
431
496
  end
432
497
  end
433
498
  end