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