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
@@ -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