activerecord-postgresql-extensions 0.2.2 → 0.3.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 +15 -0
- data/.gitignore +4 -0
- data/Gemfile +1 -0
- data/Guardfile +3 -3
- data/MIT-LICENSE +1 -1
- data/README.rdoc +10 -3
- data/lib/active_record/postgresql_extensions/adapter_extensions.rb +100 -60
- data/lib/active_record/postgresql_extensions/constraints.rb +13 -17
- data/lib/active_record/postgresql_extensions/event_triggers.rb +129 -0
- data/lib/active_record/postgresql_extensions/extensions.rb +14 -15
- data/lib/active_record/postgresql_extensions/features.rb +80 -41
- data/lib/active_record/postgresql_extensions/functions.rb +1 -1
- data/lib/active_record/postgresql_extensions/geometry.rb +6 -8
- data/lib/active_record/postgresql_extensions/indexes.rb +19 -11
- data/lib/active_record/postgresql_extensions/languages.rb +1 -1
- data/lib/active_record/postgresql_extensions/materialized_views.rb +272 -0
- data/lib/active_record/postgresql_extensions/permissions.rb +60 -22
- data/lib/active_record/postgresql_extensions/roles.rb +18 -7
- data/lib/active_record/postgresql_extensions/rules.rb +5 -0
- data/lib/active_record/postgresql_extensions/schemas.rb +39 -3
- data/lib/active_record/postgresql_extensions/sequences.rb +6 -3
- data/lib/active_record/postgresql_extensions/tables.rb +47 -19
- data/lib/active_record/postgresql_extensions/tablespaces.rb +1 -1
- data/lib/active_record/postgresql_extensions/text_search.rb +3 -3
- data/lib/active_record/postgresql_extensions/triggers.rb +3 -3
- data/lib/active_record/postgresql_extensions/types.rb +104 -1
- data/lib/active_record/postgresql_extensions/utils.rb +35 -13
- data/lib/active_record/postgresql_extensions/vacuum.rb +1 -1
- data/lib/active_record/postgresql_extensions/version.rb +1 -1
- data/lib/active_record/postgresql_extensions/views.rb +137 -6
- data/lib/activerecord-postgresql-extensions.rb +13 -11
- data/test/{adapter_tests.rb → adapter_extensions_tests.rb} +96 -3
- data/test/constraints_tests.rb +216 -104
- data/test/event_triggers_tests.rb +109 -0
- data/test/extensions_tests.rb +47 -39
- data/test/functions_tests.rb +47 -38
- data/test/geometry_tests.rb +268 -135
- data/test/{index_tests.rb → indexes_tests.rb} +16 -16
- data/test/languages_tests.rb +26 -9
- data/test/materialized_views_tests.rb +174 -0
- data/test/permissions_tests.rb +159 -45
- data/test/roles_tests.rb +17 -7
- data/test/rules_tests.rb +14 -6
- data/test/schemas_tests.rb +35 -9
- data/test/sequences_tests.rb +9 -11
- data/test/tables_tests.rb +132 -42
- data/test/tablespace_tests.rb +21 -15
- data/test/test_helper.rb +56 -10
- data/test/text_search_tests.rb +42 -44
- data/test/trigger_tests.rb +1 -3
- data/test/types_tests.rb +95 -0
- data/test/vacuum_tests.rb +1 -3
- data/test/views_tests.rb +203 -0
- metadata +22 -16
@@ -53,6 +53,11 @@ module ActiveRecord
|
|
53
53
|
def drop_rule(name, table)
|
54
54
|
execute "DROP RULE #{quote_rule(name)} ON #{quote_table_name(table)};"
|
55
55
|
end
|
56
|
+
|
57
|
+
# Renames a rule. Available in PostgreSQL 9.3+.
|
58
|
+
def rename_rule(old_name, table, new_name)
|
59
|
+
execute "ALTER RULE #{quote_rule(old_name)} ON #{quote_table_name(table)} RENAME TO #{quote_rule(new_name)};"
|
60
|
+
end
|
56
61
|
end
|
57
62
|
|
58
63
|
# Creates a PostgreSQL rule.
|
@@ -15,8 +15,18 @@ module ActiveRecord
|
|
15
15
|
# * <tt>:authorization</tt> - adds an AUTHORIZATION clause. This is
|
16
16
|
# used to set the owner of the schema. This can be changed with
|
17
17
|
# alter_schema_owner as necessary.
|
18
|
+
# * <tt>:if_not_exists</tt> - adds an IF NOT EXISTS clause. Available
|
19
|
+
# in PostgreSQL 9.3+.
|
18
20
|
def create_schema(schema, options = {})
|
19
|
-
sql =
|
21
|
+
sql = 'CREATE SCHEMA '
|
22
|
+
|
23
|
+
if options.key?(:if_not_exists)
|
24
|
+
ActiveRecord::PostgreSQLExtensions::Features.check_feature(:create_schema_if_not_exists)
|
25
|
+
|
26
|
+
sql << 'IF NOT EXISTS ' if options[:if_not_exists]
|
27
|
+
end
|
28
|
+
|
29
|
+
sql << quote_schema(schema)
|
20
30
|
sql << " AUTHORIZATION #{quote_role(options[:authorization])}" if options[:authorization]
|
21
31
|
execute("#{sql};")
|
22
32
|
end
|
@@ -27,10 +37,13 @@ module ActiveRecord
|
|
27
37
|
#
|
28
38
|
# * <tt>:if_exists</tt> - adds IF EXISTS.
|
29
39
|
# * <tt>:cascade</tt> - adds CASCADE.
|
30
|
-
def drop_schema(
|
40
|
+
def drop_schema(*args)
|
41
|
+
options = args.extract_options!
|
42
|
+
args.flatten!
|
43
|
+
|
31
44
|
sql = 'DROP SCHEMA '
|
32
45
|
sql << 'IF EXISTS ' if options[:if_exists]
|
33
|
-
sql << Array(
|
46
|
+
sql << Array.wrap(args).collect { |s| quote_schema(s) }.join(', ')
|
34
47
|
sql << ' CASCADE' if options[:cascade]
|
35
48
|
execute("#{sql};")
|
36
49
|
end
|
@@ -44,6 +57,29 @@ module ActiveRecord
|
|
44
57
|
def alter_schema_owner(schema, role)
|
45
58
|
execute("ALTER SCHEMA #{quote_schema(schema)} OWNER TO #{quote_role(role)};")
|
46
59
|
end
|
60
|
+
|
61
|
+
# Creates a schema based on a role name. This is a shortcut to using
|
62
|
+
# <tt>create_schema</tt> with the <tt>:authorization</tt> option and
|
63
|
+
# ensures that the schema created has the same name as the role
|
64
|
+
# specified.
|
65
|
+
#
|
66
|
+
# ==== Options
|
67
|
+
#
|
68
|
+
# * <tt>:if_not_exists</tt> - adds IF NOT EXISTS. Available in PostgreSQL
|
69
|
+
# 9.3+.
|
70
|
+
def create_schema_authorization(name, options = {})
|
71
|
+
sql = 'CREATE SCHEMA'
|
72
|
+
|
73
|
+
if options.key?(:if_not_exists)
|
74
|
+
ActiveRecord::PostgreSQLExtensions::Features.check_feature(:create_schema_if_not_exists)
|
75
|
+
|
76
|
+
sql << ' IF NOT EXISTS' if options[:if_not_exists]
|
77
|
+
end
|
78
|
+
|
79
|
+
sql << " AUTHORIZATION #{quote_role(name)}"
|
80
|
+
|
81
|
+
execute("#{sql};")
|
82
|
+
end
|
47
83
|
end
|
48
84
|
end
|
49
85
|
end
|
@@ -63,10 +63,13 @@ module ActiveRecord
|
|
63
63
|
# * <tt>:if_exists</tt> - adds IF EXISTS.
|
64
64
|
# * <tt>:cascade</tt> - cascades the operation down to objects
|
65
65
|
# referring to the sequence.
|
66
|
-
def drop_sequence(
|
66
|
+
def drop_sequence(*args)
|
67
|
+
options = args.extract_options!
|
68
|
+
args.flatten!
|
69
|
+
|
67
70
|
sql = 'DROP SEQUENCE '
|
68
71
|
sql << 'IF EXISTS ' if options[:if_exists]
|
69
|
-
sql << Array(
|
72
|
+
sql << Array.wrap(args).collect { |s| quote_sequence(s) }.join(', ')
|
70
73
|
sql << ' CASCADE' if options[:cascade]
|
71
74
|
execute("#{sql};")
|
72
75
|
end
|
@@ -116,7 +119,7 @@ module ActiveRecord
|
|
116
119
|
|
117
120
|
# Returns an Array of available sequences.
|
118
121
|
def sequences(name = nil)
|
119
|
-
query(<<-SQL, name).map { |row| row[0] }
|
122
|
+
query(PostgreSQLExtensions::Utils.strip_heredoc(<<-SQL), name).map { |row| row[0] }
|
120
123
|
SELECT c.relname AS sequencename
|
121
124
|
FROM pg_class c
|
122
125
|
WHERE c.relkind = 'S'::"char";
|
@@ -51,6 +51,9 @@ module ActiveRecord
|
|
51
51
|
# * <tt>:of_type</tt> - for "OF type_name" clauses.
|
52
52
|
# * <tt>:if_not_exists</tt> - adds the "IF NOT EXISTS" clause.
|
53
53
|
# * <tt>:unlogged</tt> - creates an UNLOGGED table.
|
54
|
+
# * <tt>:storage_parameters</tt> - a simple String or Hash used to
|
55
|
+
# assign table storage parameters. See the PostgreSQL docs for
|
56
|
+
# details on the various storage parameters available.
|
54
57
|
#
|
55
58
|
# We're expanding the doors of table definition perception with
|
56
59
|
# this exciting new addition to the world of ActiveRecord
|
@@ -112,7 +115,7 @@ module ActiveRecord
|
|
112
115
|
execute table_definition.to_s
|
113
116
|
unless table_definition.post_processing.blank?
|
114
117
|
table_definition.post_processing.each do |pp|
|
115
|
-
execute pp
|
118
|
+
execute pp.to_s
|
116
119
|
end
|
117
120
|
end
|
118
121
|
end
|
@@ -130,10 +133,13 @@ module ActiveRecord
|
|
130
133
|
# dropped as well. See the PostgreSQL documentation for details.
|
131
134
|
#
|
132
135
|
# You can still access the original method via original_drop_table.
|
133
|
-
def drop_table(
|
136
|
+
def drop_table(*args)
|
137
|
+
options = args.extract_options!
|
138
|
+
args.flatten!
|
139
|
+
|
134
140
|
sql = 'DROP TABLE '
|
135
141
|
sql << 'IF EXISTS ' if options[:if_exists]
|
136
|
-
sql << Array(
|
142
|
+
sql << Array.wrap(args).collect { |t| quote_table_name(t) }.join(', ')
|
137
143
|
sql << ' CASCADE' if options[:cascade]
|
138
144
|
execute("#{sql};")
|
139
145
|
end
|
@@ -172,10 +178,9 @@ module ActiveRecord
|
|
172
178
|
# been created. See the source code for PostgreSQLAdapter#create_table
|
173
179
|
# and PostgreSQLTableDefinition#geometry for an example of its use.
|
174
180
|
class PostgreSQLTableDefinition < TableDefinition
|
175
|
-
attr_accessor :base, :table_name, :options
|
181
|
+
attr_accessor :base, :table_name, :options
|
176
182
|
|
177
183
|
def initialize(base, table_name, options = {}) #:nodoc:
|
178
|
-
@table_constraints = Array.new
|
179
184
|
@table_name, @options = table_name, options
|
180
185
|
super(base)
|
181
186
|
end
|
@@ -193,6 +198,12 @@ module ActiveRecord
|
|
193
198
|
end
|
194
199
|
end
|
195
200
|
|
201
|
+
if options.key?(:if_not_exists)
|
202
|
+
ActiveRecord::PostgreSQLExtensions::Features.check_feature(:create_table_if_not_exists)
|
203
|
+
elsif options.key?(:unlogged)
|
204
|
+
ActiveRecord::PostgreSQLExtensions::Features.check_feature(:create_table_unlogged)
|
205
|
+
end
|
206
|
+
|
196
207
|
unless options[:id] == false
|
197
208
|
self.primary_key(options[:primary_key] || Base.get_primary_key(table_name))
|
198
209
|
|
@@ -213,7 +224,7 @@ module ActiveRecord
|
|
213
224
|
ary << @columns.collect(&:to_sql)
|
214
225
|
ary << @like if defined?(@like) && @like
|
215
226
|
end
|
216
|
-
ary <<
|
227
|
+
ary << table_constraints unless table_constraints.empty?
|
217
228
|
|
218
229
|
unless ary.empty?
|
219
230
|
sql << " (\n "
|
@@ -221,7 +232,8 @@ module ActiveRecord
|
|
221
232
|
sql << "\n)"
|
222
233
|
end
|
223
234
|
|
224
|
-
sql << "\nINHERITS (" << Array(options[:inherits]).collect { |i| base.quote_table_name(i) }.join(', ') << ')' if options[:inherits]
|
235
|
+
sql << "\nINHERITS (" << Array.wrap(options[:inherits]).collect { |i| base.quote_table_name(i) }.join(', ') << ')' if options[:inherits]
|
236
|
+
sql << "\nWITH (#{ActiveRecord::PostgreSQLExtensions::Utils.options_from_hash_or_string(options[:storage_parameters], base)})" if options[:storage_parameters].present?
|
225
237
|
sql << "\nON COMMIT #{options[:on_commit].to_s.upcase.gsub(/_/, ' ')}" if options[:on_commit]
|
226
238
|
sql << "\n#{options[:options]}" if options[:options]
|
227
239
|
sql << "\nTABLESPACE #{base.quote_tablespace(options[:tablespace])}" if options[:tablespace]
|
@@ -253,11 +265,11 @@ module ActiveRecord
|
|
253
265
|
@like = "LIKE #{@base.quote_table_name(parent_table)}"
|
254
266
|
|
255
267
|
if options[:including]
|
256
|
-
@like << Array(options[:including]).collect { |l| " INCLUDING #{l.to_s.upcase}" }.join
|
268
|
+
@like << Array.wrap(options[:including]).collect { |l| " INCLUDING #{l.to_s.upcase}" }.join
|
257
269
|
end
|
258
270
|
|
259
271
|
if options[:excluding]
|
260
|
-
@like << Array(options[:excluding]).collect { |l| " EXCLUDING #{l.to_s.upcase}" }.join
|
272
|
+
@like << Array.wrap(options[:excluding]).collect { |l| " EXCLUDING #{l.to_s.upcase}" }.join
|
261
273
|
end
|
262
274
|
@like
|
263
275
|
end
|
@@ -265,29 +277,29 @@ module ActiveRecord
|
|
265
277
|
# Add a CHECK constraint to the table. See
|
266
278
|
# PostgreSQLCheckConstraint for more details.
|
267
279
|
def check_constraint(expression, options = {})
|
268
|
-
|
280
|
+
table_constraints << PostgreSQLCheckConstraint.new(@base, expression, options)
|
269
281
|
end
|
270
282
|
|
271
283
|
# Add a UNIQUE constraint to the table. See
|
272
284
|
# PostgreSQLUniqueConstraint for more details.
|
273
285
|
def unique_constraint(columns, options = {})
|
274
|
-
|
286
|
+
table_constraints << PostgreSQLUniqueConstraint.new(@base, columns, options)
|
275
287
|
end
|
276
288
|
|
277
289
|
# Add a FOREIGN KEY constraint to the table. See
|
278
290
|
# PostgreSQLForeignKeyConstraint for more details.
|
279
291
|
def foreign_key(columns, ref_table, *args)
|
280
|
-
|
292
|
+
table_constraints << PostgreSQLForeignKeyConstraint.new(@base, columns, ref_table, *args)
|
281
293
|
end
|
282
294
|
|
283
295
|
# Add an EXCLUDE constraint to the table. See PostgreSQLExcludeConstraint
|
284
296
|
# for more details.
|
285
297
|
def exclude(excludes, options = {})
|
286
|
-
|
298
|
+
table_constraints << PostgreSQLExcludeConstraint.new(@base, table_name, excludes, options)
|
287
299
|
end
|
288
300
|
|
289
301
|
def primary_key_constraint(columns, options = {})
|
290
|
-
|
302
|
+
table_constraints << PostgreSQLPrimaryKeyConstraint.new(@base, columns, options)
|
291
303
|
end
|
292
304
|
|
293
305
|
def column_with_constraints(name, type, *args) #:nodoc:
|
@@ -305,7 +317,7 @@ module ActiveRecord
|
|
305
317
|
check
|
306
318
|
end
|
307
319
|
|
308
|
-
|
320
|
+
table_constraints << check.collect do |c|
|
309
321
|
if c.is_a?(Hash)
|
310
322
|
PostgreSQLCheckConstraint.new(@base, c.delete(:expression), c)
|
311
323
|
else
|
@@ -321,7 +333,7 @@ module ActiveRecord
|
|
321
333
|
[ references, {} ]
|
322
334
|
end
|
323
335
|
|
324
|
-
|
336
|
+
table_constraints << PostgreSQLForeignKeyConstraint.new(
|
325
337
|
@base,
|
326
338
|
name,
|
327
339
|
ref_table,
|
@@ -333,26 +345,42 @@ module ActiveRecord
|
|
333
345
|
unless unique.is_a?(Hash)
|
334
346
|
unique = {}
|
335
347
|
end
|
336
|
-
|
348
|
+
table_constraints << PostgreSQLUniqueConstraint.new(@base, name, unique)
|
337
349
|
end
|
338
350
|
|
339
351
|
if primary_key
|
340
352
|
unless primary_key.is_a?(Hash)
|
341
353
|
primary_key = {}
|
342
354
|
end
|
343
|
-
|
355
|
+
table_constraints << PostgreSQLPrimaryKeyConstraint.new(@base, name, primary_key)
|
344
356
|
end
|
345
357
|
|
346
358
|
self
|
347
359
|
end
|
348
360
|
alias_method_chain :column, :constraints
|
349
361
|
|
362
|
+
# Add an INDEX to the table. This INDEX will be added during post
|
363
|
+
# processing after the table has been created. See
|
364
|
+
# PostgreSQLIndexDefinition for more details.
|
365
|
+
def index(name, columns, options = {})
|
366
|
+
post_processing << PostgreSQLIndexDefinition.new(@base, name, self.table_name, columns, options)
|
367
|
+
end
|
368
|
+
|
369
|
+
# Add statements to execute to after a table has been created.
|
370
|
+
def post_processing
|
371
|
+
@post_processing ||= []
|
372
|
+
end
|
373
|
+
|
350
374
|
private
|
351
375
|
LIKE_TYPES = %w{ defaults constraints indexes }.freeze
|
352
376
|
|
377
|
+
def table_constraints
|
378
|
+
@table_constraints ||= []
|
379
|
+
end
|
380
|
+
|
353
381
|
def assert_valid_like_types(likes) #:nodoc:
|
354
382
|
unless likes.blank?
|
355
|
-
check_likes = Array(likes).collect(&:to_s) - LIKE_TYPES
|
383
|
+
check_likes = Array.wrap(likes).collect(&:to_s) - LIKE_TYPES
|
356
384
|
if !check_likes.empty?
|
357
385
|
raise ActiveRecord::InvalidLikeTypes.new(check_likes)
|
358
386
|
end
|
@@ -76,7 +76,7 @@ module ActiveRecord
|
|
76
76
|
|
77
77
|
def assert_valid_tablespace_parameter(parameter)
|
78
78
|
if !TABLESPACE_PARAMETERS.include? parameter.to_s.downcase
|
79
|
-
raise ActiveRecord::InvalidTablespaceParameter.new(
|
79
|
+
raise ActiveRecord::InvalidTablespaceParameter.new(parameter)
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end
|
@@ -54,13 +54,13 @@ module ActiveRecord
|
|
54
54
|
add_or_alter = options[:action].to_s.upcase
|
55
55
|
|
56
56
|
sql = "ALTER TEXT SEARCH CONFIGURATION #{quote_generic_with_schema(name)} #{add_or_alter} MAPPING FOR "
|
57
|
-
sql << Array(tokens).collect { |token|
|
57
|
+
sql << Array.wrap(tokens).collect { |token|
|
58
58
|
quote_generic(token)
|
59
59
|
}.join(', ')
|
60
60
|
|
61
61
|
sql << ' WITH '
|
62
62
|
|
63
|
-
sql << Array(dictionaries).collect { |dictionary|
|
63
|
+
sql << Array.wrap(dictionaries).collect { |dictionary|
|
64
64
|
quote_generic(dictionary)
|
65
65
|
}.join(', ')
|
66
66
|
|
@@ -80,7 +80,7 @@ module ActiveRecord
|
|
80
80
|
end
|
81
81
|
|
82
82
|
sql = "ALTER TEXT SEARCH CONFIGURATION #{quote_generic_with_schema(name)} ALTER MAPPING FOR "
|
83
|
-
sql << Array(mappings).collect { |token_type|
|
83
|
+
sql << Array.wrap(mappings).collect { |token_type|
|
84
84
|
quote_generic(token_type)
|
85
85
|
}.join(', ')
|
86
86
|
sql << " REPLACE #{quote_generic(old_dictionary)} WITH #{quote_generic(new_dictionary)}"
|
@@ -117,8 +117,8 @@ module ActiveRecord
|
|
117
117
|
|
118
118
|
def to_sql #:nodoc:
|
119
119
|
sql = "CREATE TRIGGER #{base.quote_generic(name)} #{called.to_s.upcase} "
|
120
|
-
sql << Array(events).collect { |e| e.to_s.upcase }.join(' OR ')
|
121
|
-
sql << " OF " << Array(options[:of]).collect { |o| base.quote_generic(o) }.join(', ') if options[:of].present?
|
120
|
+
sql << Array.wrap(events).collect { |e| e.to_s.upcase }.join(' OR ')
|
121
|
+
sql << " OF " << Array.wrap(options[:of]).collect { |o| base.quote_generic(o) }.join(', ') if options[:of].present?
|
122
122
|
sql << " ON #{base.quote_table_name(table)}"
|
123
123
|
sql << " FOR EACH #{options[:for_each].to_s.upcase}" if options[:for_each]
|
124
124
|
sql << " EXECUTE PROCEDURE #{base.quote_function(function)}(#{options[:args]})"
|
@@ -138,7 +138,7 @@ module ActiveRecord
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def assert_valid_events(events) #:nodoc:
|
141
|
-
check_events = Array(events).collect(&:to_s) - EVENT_TYPES
|
141
|
+
check_events = Array.wrap(events).collect(&:to_s) - EVENT_TYPES
|
142
142
|
if !check_events.empty?
|
143
143
|
raise ActiveRecord::InvalidTriggerEvent.new(check_events)
|
144
144
|
end
|
@@ -2,16 +2,119 @@
|
|
2
2
|
require 'active_record/connection_adapters/postgresql_adapter'
|
3
3
|
|
4
4
|
module ActiveRecord
|
5
|
+
class InvalidAddEnumValueOptions < ActiveRecordError #:nodoc:
|
6
|
+
end
|
7
|
+
|
5
8
|
module ConnectionAdapters
|
6
9
|
class PostgreSQLAdapter
|
7
10
|
# Returns an Array of available languages.
|
8
11
|
def types(name = nil)
|
9
|
-
query(
|
12
|
+
query(PostgreSQLExtensions::Utils.strip_heredoc(<<-SQL), name).map(&:first)
|
13
|
+
SELECT t.typname as type
|
14
|
+
FROM pg_type t
|
15
|
+
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
16
|
+
WHERE (t.typrelid = 0 OR (
|
17
|
+
SELECT c.relkind = 'c'
|
18
|
+
FROM pg_catalog.pg_class c
|
19
|
+
WHERE c.oid = t.typrelid
|
20
|
+
)) AND
|
21
|
+
NOT EXISTS(
|
22
|
+
SELECT 1
|
23
|
+
FROM pg_catalog.pg_type el
|
24
|
+
WHERE el.oid = t.typelem
|
25
|
+
AND el.typarray = t.oid
|
26
|
+
) AND
|
27
|
+
n.nspname NOT IN ('information_schema');
|
28
|
+
SQL
|
10
29
|
end
|
11
30
|
|
12
31
|
def type_exists?(name)
|
13
32
|
types.include?(name.to_s)
|
14
33
|
end
|
34
|
+
|
35
|
+
# Creates an ENUM TYPE. An ENUM can contain zero or more values. ENUMs
|
36
|
+
# can be dropped with #drop_type.
|
37
|
+
def create_enum(name, *values)
|
38
|
+
execute PostgreSQLEnumDefinition.new(self, name, *values).to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
# Adds a new value to an ENUM.
|
42
|
+
#
|
43
|
+
# ==== Options
|
44
|
+
#
|
45
|
+
# * <tt>:before</tt> - add the new value before this value.
|
46
|
+
# * <tt>:after</tt> - add the new value after this value.
|
47
|
+
# * <tt>:if_not_exists</tt> - adds the value if it doesn't already
|
48
|
+
# exist. Available in PostgreSQL 9.3+.
|
49
|
+
def add_enum_value(enum, value, options = {})
|
50
|
+
assert_valid_add_enum_value_options(options)
|
51
|
+
|
52
|
+
sql = "ALTER TYPE #{quote_generic(enum)} ADD VALUE"
|
53
|
+
|
54
|
+
if options.key?(:if_not_exists)
|
55
|
+
ActiveRecord::PostgreSQLExtensions::Features.check_feature(:type_if_not_exists)
|
56
|
+
|
57
|
+
sql << " IF NOT EXISTS" if options[:if_not_exists]
|
58
|
+
end
|
59
|
+
|
60
|
+
sql << " #{quote(value)}"
|
61
|
+
|
62
|
+
if options[:before]
|
63
|
+
sql << " BEFORE #{quote(options[:before])}"
|
64
|
+
elsif options[:after]
|
65
|
+
sql << " AFTER #{quote(options[:after])}"
|
66
|
+
end
|
67
|
+
|
68
|
+
execute("#{sql};")
|
69
|
+
end
|
70
|
+
|
71
|
+
# Drop TYPEs.
|
72
|
+
#
|
73
|
+
# ==== Options
|
74
|
+
#
|
75
|
+
# * <tt>:if_exists</tt> - adds IF EXISTS.
|
76
|
+
# * <tt>:cascade</tt> - adds CASCADE.
|
77
|
+
def drop_type(*args)
|
78
|
+
options = args.extract_options!
|
79
|
+
args.flatten!
|
80
|
+
|
81
|
+
sql = 'DROP TYPE '
|
82
|
+
sql << 'IF EXISTS ' if options[:if_exists]
|
83
|
+
sql << Array.wrap(args).collect { |i| quote_generic(i) }.join(', ')
|
84
|
+
sql << ' CASCADE' if options[:cascade]
|
85
|
+
execute("#{sql};")
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns an Array of possible
|
89
|
+
def enum_values(name)
|
90
|
+
query(%{SELECT unnest(enum_range(NULL::#{quote_generic(name)}))}, 'Enum values').map(&:first)
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
def assert_valid_add_enum_value_options(options)
|
95
|
+
if options[:before] && options[:after]
|
96
|
+
raise InvalidAddEnumValueOptions.new("Can't use both :before and :after options together")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Creates a PostgreSQL enum type definition. This class isn't really meant
|
103
|
+
# to be used directly. Instead, see PostgreSQLAdapter#create_enum for
|
104
|
+
# usage.
|
105
|
+
class PostgreSQLEnumDefinition
|
106
|
+
attr_accessor :base, :name, :values
|
107
|
+
|
108
|
+
def initialize(base, name, *values)
|
109
|
+
@base, @name, @values = base, name, values.flatten
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_sql #:nodoc:
|
113
|
+
sql = "CREATE TYPE #{base.quote_generic(name)} AS ENUM ("
|
114
|
+
sql << values.collect { |t| base.quote(t.to_s) }.join(', ')
|
115
|
+
sql << ")"
|
116
|
+
"#{sql};"
|
15
117
|
end
|
118
|
+
alias :to_s :to_sql
|
16
119
|
end
|
17
120
|
end
|