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.
- checksums.yaml +4 -4
- data/lib/active_record/connection_adapters/redshift/array_parser.rb +35 -39
- data/lib/active_record/connection_adapters/redshift/column.rb +10 -0
- data/lib/active_record/connection_adapters/redshift/database_statements.rb +37 -47
- data/lib/active_record/connection_adapters/redshift/oid.rb +14 -359
- data/lib/active_record/connection_adapters/redshift/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/redshift/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/redshift/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/redshift/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/redshift/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/redshift/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/redshift/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/redshift/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/redshift/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/redshift/oid/type_map_initializer.rb +63 -0
- data/lib/active_record/connection_adapters/redshift/quoting.rb +45 -119
- data/lib/active_record/connection_adapters/redshift/referential_integrity.rb +4 -19
- data/lib/active_record/connection_adapters/redshift/schema_definitions.rb +73 -0
- data/lib/active_record/connection_adapters/redshift/schema_statements.rb +141 -76
- data/lib/active_record/connection_adapters/redshift/utils.rb +77 -0
- data/lib/active_record/connection_adapters/redshift_adapter.rb +252 -496
- metadata +17 -11
- data/lib/active_record/connection_adapters/redshift/cast.rb +0 -156
@@ -1,28 +1,13 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module ConnectionAdapters
|
3
|
-
|
4
|
-
module ReferentialIntegrity
|
5
|
-
def supports_disable_referential_integrity?
|
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
|
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
|
-
|
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
|
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
|
-
|
37
|
-
|
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
|
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.
|
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|
|
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
|
-
|
105
|
-
return false unless
|
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
|
115
|
-
AND c.relname = '#{
|
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 =
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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
|
-
|
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
|
-
|
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)\
|
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].
|
495
|
+
[super, *order_columns].join(', ')
|
431
496
|
end
|
432
497
|
end
|
433
498
|
end
|