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
@@ -2,22 +2,44 @@
|
|
2
2
|
module ActiveRecord
|
3
3
|
module PostgreSQLExtensions
|
4
4
|
module Utils
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
raise ArgumentError.new("Expected an Array of Hashes")
|
13
|
-
else
|
14
|
-
arg
|
15
|
-
end
|
5
|
+
def hash_or_array_of_hashes(arg)
|
6
|
+
case arg
|
7
|
+
when Hash
|
8
|
+
[ arg ]
|
9
|
+
when Array
|
10
|
+
if arg.detect { |e| !e.is_a?(Hash) }
|
11
|
+
raise ArgumentError.new("Expected an Array of Hashes")
|
16
12
|
else
|
17
|
-
|
18
|
-
|
13
|
+
arg
|
14
|
+
end
|
15
|
+
else
|
16
|
+
raise ArgumentError.new("Expected either a Hash or an Array of Hashes")
|
19
17
|
end
|
20
18
|
end
|
19
|
+
|
20
|
+
def strip_heredoc(str)
|
21
|
+
indent = str.scan(/^[ \t]*(?=\S)/).min.try(:size) || 0
|
22
|
+
str.gsub(/^[ \t]{#{indent}}/, '').strip
|
23
|
+
end
|
24
|
+
|
25
|
+
def options_from_hash_or_string(value, base = self.base)
|
26
|
+
case value
|
27
|
+
when Hash
|
28
|
+
value.collect { |(k, v)|
|
29
|
+
"#{base.quote_generic(k)} = #{base.quote(v)}"
|
30
|
+
}.join(', ')
|
31
|
+
|
32
|
+
when String
|
33
|
+
value
|
34
|
+
|
35
|
+
else
|
36
|
+
value.to_s
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class << self
|
41
|
+
include Utils
|
42
|
+
end
|
21
43
|
end
|
22
44
|
end
|
23
45
|
end
|
@@ -88,7 +88,7 @@ module ActiveRecord
|
|
88
88
|
sql << " #{base.quote_table_name(table)}" if self.table
|
89
89
|
|
90
90
|
if options[:columns]
|
91
|
-
sql << ' (' << Array(options[:columns]).collect { |column|
|
91
|
+
sql << ' (' << Array.wrap(options[:columns]).collect { |column|
|
92
92
|
base.quote_column_name(column)
|
93
93
|
}.join(', ') << ')'
|
94
94
|
end
|
@@ -27,6 +27,11 @@ module ActiveRecord
|
|
27
27
|
# necessary. Note that this can be an Array and that it must be
|
28
28
|
# the same length as the number of output columns created by
|
29
29
|
# +query+.
|
30
|
+
# * <tt>:with_options</tt> - sets view options. View options were added
|
31
|
+
# in PostgreSQL 9.1. See the PostgreSQL docs for details on the
|
32
|
+
# available options.
|
33
|
+
# * <tt>:recursive</tt> - adds the RECURSIVE clause. Available in
|
34
|
+
# PostgreSQL 9.3+.
|
30
35
|
#
|
31
36
|
# ==== Examples
|
32
37
|
#
|
@@ -49,27 +54,68 @@ module ActiveRecord
|
|
49
54
|
#
|
50
55
|
# * <tt>:if_exists</tt> - adds IF EXISTS.
|
51
56
|
# * <tt>:cascade</tt> - adds CASCADE.
|
52
|
-
def drop_view(
|
57
|
+
def drop_view(*args)
|
58
|
+
options = args.extract_options!
|
59
|
+
args.flatten!
|
60
|
+
|
53
61
|
sql = 'DROP VIEW '
|
54
62
|
sql << 'IF EXISTS ' if options[:if_exists]
|
55
|
-
sql << Array(
|
63
|
+
sql << Array.wrap(args).collect { |v| quote_view_name(v) }.join(', ')
|
56
64
|
sql << ' CASCADE' if options[:cascade]
|
57
65
|
execute("#{sql};")
|
58
66
|
end
|
59
67
|
|
60
68
|
# Renames a view.
|
61
69
|
def rename_view(name, new_name, options = {})
|
62
|
-
execute
|
70
|
+
execute PostgreSQLViewAlterer.new(self, name, {
|
71
|
+
:rename_to => new_name
|
72
|
+
}, options).to_sql
|
63
73
|
end
|
64
74
|
|
65
75
|
# Change the ownership of a view.
|
66
76
|
def alter_view_owner(name, role, options = {})
|
67
|
-
execute
|
77
|
+
execute PostgreSQLViewAlterer.new(self, name, {
|
78
|
+
:owner_to => role
|
79
|
+
}, options).to_sql
|
68
80
|
end
|
69
81
|
|
70
82
|
# Alter a view's schema.
|
71
83
|
def alter_view_schema(name, schema, options = {})
|
72
|
-
execute
|
84
|
+
execute PostgreSQLViewAlterer.new(self, name, {
|
85
|
+
:set_schema => schema
|
86
|
+
}, options).to_sql
|
87
|
+
end
|
88
|
+
|
89
|
+
# Sets a view's options using a Hash.
|
90
|
+
def alter_view_set_options(name, set_options, options = {})
|
91
|
+
execute PostgreSQLViewAlterer.new(self, name, {
|
92
|
+
:set_options => set_options
|
93
|
+
}, options).to_sql
|
94
|
+
end
|
95
|
+
|
96
|
+
# Resets a view's options.
|
97
|
+
def alter_view_reset_options(name, *args)
|
98
|
+
options = args.extract_options!
|
99
|
+
|
100
|
+
execute PostgreSQLViewAlterer.new(self, name, {
|
101
|
+
:reset_options => args
|
102
|
+
}, options).to_sql
|
103
|
+
end
|
104
|
+
|
105
|
+
# Set a column default on a view.
|
106
|
+
def alter_view_set_column_default(name, column, expression, options = {})
|
107
|
+
execute PostgreSQLViewAlterer.new(self, name, {
|
108
|
+
:set_default => {
|
109
|
+
column => expression
|
110
|
+
}
|
111
|
+
}, options).to_sql
|
112
|
+
end
|
113
|
+
|
114
|
+
# Drop a column default from a view.
|
115
|
+
def alter_view_drop_column_default(name, column, options = {})
|
116
|
+
execute PostgreSQLViewAlterer.new(self, name, {
|
117
|
+
:drop_default => column
|
118
|
+
}, options).to_sql
|
73
119
|
end
|
74
120
|
end
|
75
121
|
|
@@ -77,6 +123,8 @@ module ActiveRecord
|
|
77
123
|
# to be used directly. Instead, see PostgreSQLAdapter#create_view
|
78
124
|
# for usage.
|
79
125
|
class PostgreSQLViewDefinition
|
126
|
+
include ActiveRecord::PostgreSQLExtensions::Utils
|
127
|
+
|
80
128
|
attr_accessor :base, :name, :query, :options
|
81
129
|
|
82
130
|
def initialize(base, name, query, options = {}) #:nodoc:
|
@@ -87,16 +135,99 @@ module ActiveRecord
|
|
87
135
|
sql = 'CREATE '
|
88
136
|
sql << 'OR REPLACE ' if options[:replace]
|
89
137
|
sql << 'TEMPORARY ' if options[:temporary]
|
138
|
+
sql << 'RECURSIVE ' if options[:recursive]
|
90
139
|
sql << "VIEW #{base.quote_view_name(name)} "
|
140
|
+
|
91
141
|
if options[:columns]
|
92
|
-
sql << '(' << Array(options[:columns]).collect do |c|
|
142
|
+
sql << '(' << Array.wrap(options[:columns]).collect do |c|
|
93
143
|
base.quote_column_name(c)
|
94
144
|
end.join(', ') << ') '
|
95
145
|
end
|
146
|
+
|
147
|
+
if options[:with_options]
|
148
|
+
ActiveRecord::PostgreSQLExtensions::Features.check_feature(:view_set_options)
|
149
|
+
|
150
|
+
sql << "WITH (#{options_from_hash_or_string(options[:with_options])}) " if options.present?
|
151
|
+
end
|
152
|
+
|
96
153
|
sql << "AS #{query}"
|
97
154
|
"#{sql};"
|
98
155
|
end
|
99
156
|
alias :to_s :to_sql
|
100
157
|
end
|
158
|
+
|
159
|
+
class PostgreSQLViewAlterer
|
160
|
+
include ActiveRecord::PostgreSQLExtensions::Utils
|
161
|
+
|
162
|
+
attr_accessor :base, :name, :actions, :options
|
163
|
+
|
164
|
+
VALID_OPTIONS = %w{
|
165
|
+
set_default
|
166
|
+
drop_default
|
167
|
+
owner_to
|
168
|
+
rename_to
|
169
|
+
set_schema
|
170
|
+
set_options
|
171
|
+
reset_options
|
172
|
+
}.freeze
|
173
|
+
|
174
|
+
def initialize(base, name, actions, options = {}) #:nodoc:
|
175
|
+
@base, @name, @actions, @options = base, name, actions, options
|
176
|
+
end
|
177
|
+
|
178
|
+
def to_sql #:nodoc:
|
179
|
+
all_sql = []
|
180
|
+
|
181
|
+
VALID_OPTIONS.each do |key|
|
182
|
+
key = key.to_sym
|
183
|
+
|
184
|
+
if actions.key?(key)
|
185
|
+
sql = "ALTER VIEW "
|
186
|
+
|
187
|
+
if options.key?(:if_exists)
|
188
|
+
ActiveRecord::PostgreSQLExtensions::Features.check_feature(:view_if_exists)
|
189
|
+
|
190
|
+
sql << "IF EXISTS " if options[:if_exists]
|
191
|
+
end
|
192
|
+
|
193
|
+
sql << "#{base.quote_view_name(name)} "
|
194
|
+
|
195
|
+
sql << case key
|
196
|
+
when :set_default
|
197
|
+
column, expression = actions[:set_default].flatten
|
198
|
+
"ALTER COLUMN #{base.quote_column_name(column)} SET DEFAULT #{expression}"
|
199
|
+
|
200
|
+
when :drop_default
|
201
|
+
"ALTER COLUMN #{base.quote_column_name(actions[:drop_default])} DROP DEFAULT"
|
202
|
+
|
203
|
+
when :owner_to
|
204
|
+
"OWNER TO #{base.quote_role(actions[:owner_to])}"
|
205
|
+
|
206
|
+
when :rename_to
|
207
|
+
"RENAME TO #{base.quote_generic_ignore_scoped_schema(actions[:rename_to])}"
|
208
|
+
|
209
|
+
when :set_schema
|
210
|
+
"SET SCHEMA #{base.quote_schema(actions[:set_schema])}"
|
211
|
+
|
212
|
+
when :set_options
|
213
|
+
ActiveRecord::PostgreSQLExtensions::Features.check_feature(:view_set_options)
|
214
|
+
|
215
|
+
"SET (#{options_from_hash_or_string(actions[:set_options])})" if actions[:set_options].present?
|
216
|
+
|
217
|
+
when :reset_options
|
218
|
+
ActiveRecord::PostgreSQLExtensions::Features.check_feature(:view_set_options)
|
219
|
+
|
220
|
+
'RESET (' << Array.wrap(actions[:reset_options]).collect { |value|
|
221
|
+
base.quote_generic(value)
|
222
|
+
}.join(", ") << ')'
|
223
|
+
end
|
224
|
+
|
225
|
+
all_sql << "#{sql};"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
all_sql.join("\n")
|
230
|
+
end
|
231
|
+
end
|
101
232
|
end
|
102
233
|
end
|
@@ -22,24 +22,26 @@ dirname = File.join(File.dirname(__FILE__), *%w{ active_record postgresql_extens
|
|
22
22
|
features
|
23
23
|
adapter_extensions
|
24
24
|
constraints
|
25
|
-
|
26
|
-
|
25
|
+
extensions
|
26
|
+
foreign_key_associations
|
27
|
+
functions
|
28
|
+
event_triggers
|
29
|
+
geometry
|
27
30
|
indexes
|
28
|
-
permissions
|
29
|
-
schemas
|
30
31
|
languages
|
32
|
+
materialized_views
|
33
|
+
permissions
|
34
|
+
roles
|
31
35
|
rules
|
32
|
-
|
36
|
+
schemas
|
33
37
|
sequences
|
38
|
+
tables
|
39
|
+
tablespaces
|
40
|
+
text_search
|
34
41
|
triggers
|
35
|
-
views
|
36
|
-
geometry
|
37
42
|
types
|
38
|
-
roles
|
39
|
-
text_search
|
40
|
-
extensions
|
41
|
-
foreign_key_associations
|
42
43
|
vacuum
|
44
|
+
views
|
43
45
|
}.each do |file|
|
44
46
|
require File.join(dirname, file)
|
45
47
|
end
|
@@ -2,9 +2,7 @@
|
|
2
2
|
$: << File.dirname(__FILE__)
|
3
3
|
require 'test_helper'
|
4
4
|
|
5
|
-
class AdapterExtensionTests <
|
6
|
-
include PostgreSQLExtensionsTestHelper
|
7
|
-
|
5
|
+
class AdapterExtensionTests < PostgreSQLExtensionsTestCase
|
8
6
|
def test_quote_table_name_with_schema_string
|
9
7
|
assert_equal(%{"foo"."bar"}, ARBC.quote_table_name('foo.bar'))
|
10
8
|
end
|
@@ -207,4 +205,99 @@ class AdapterExtensionTests < MiniTest::Unit::TestCase
|
|
207
205
|
%{ALTER TABLE "foo" ALTER "bar" DROP NOT NULL},
|
208
206
|
], statements)
|
209
207
|
end
|
208
|
+
|
209
|
+
def stub_copy_from
|
210
|
+
if ARBC.raw_connection.respond_to?(:copy_data)
|
211
|
+
ARBC.raw_connection.stub(:copy_data, proc { |sql|
|
212
|
+
statements << sql
|
213
|
+
}) do
|
214
|
+
yield
|
215
|
+
end
|
216
|
+
else
|
217
|
+
yield
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def test_copy_from
|
222
|
+
stub_copy_from do
|
223
|
+
Mig.copy_from(:foo, '/dev/null') rescue nil
|
224
|
+
Mig.copy_from(:foo, '/dev/null', :columns => :name) rescue nil
|
225
|
+
Mig.copy_from(:foo, '/dev/null', :columns => [ :name, :description ]) rescue nil
|
226
|
+
Mig.copy_from(:foo, '/dev/null', :local => true) rescue nil
|
227
|
+
Mig.copy_from(:foo, '/dev/null', :binary => true) rescue nil
|
228
|
+
Mig.copy_from(:foo, '/dev/null', :csv => true) rescue nil
|
229
|
+
Mig.copy_from(:foo, '/dev/null', :csv => { :header => true, :quote => '|', :escape => '&' }) rescue nil
|
230
|
+
Mig.copy_from(:foo, '/dev/null', :local => false)
|
231
|
+
end
|
232
|
+
|
233
|
+
assert_equal([
|
234
|
+
%{COPY "foo" FROM STDIN;},
|
235
|
+
%{COPY "foo" ("name") FROM STDIN;},
|
236
|
+
%{COPY "foo" ("name", "description") FROM STDIN;},
|
237
|
+
%{COPY "foo" FROM STDIN;},
|
238
|
+
%{COPY "foo" FROM STDIN BINARY;},
|
239
|
+
%{COPY "foo" FROM STDIN CSV;},
|
240
|
+
%{COPY "foo" FROM STDIN CSV HEADER QUOTE AS '|' ESCAPE AS '&';},
|
241
|
+
%{COPY "foo" FROM '/dev/null';}
|
242
|
+
], statements)
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_copy_from_with_freeze_option
|
246
|
+
skip unless ActiveRecord::PostgreSQLExtensions::Features.copy_from_freeze?
|
247
|
+
|
248
|
+
stub_copy_from do
|
249
|
+
Mig.copy_from(:foo, '/dev/null', :freeze => true) rescue nil
|
250
|
+
end
|
251
|
+
|
252
|
+
assert_equal([
|
253
|
+
%{COPY "foo" FROM STDIN FREEZE;}
|
254
|
+
], statements)
|
255
|
+
end
|
256
|
+
|
257
|
+
def test_copy_from_with_encoding_option
|
258
|
+
skip unless ActiveRecord::PostgreSQLExtensions::Features.copy_from_encoding?
|
259
|
+
|
260
|
+
stub_copy_from do
|
261
|
+
Mig.copy_from(:foo, '/dev/null', :encoding => 'UTF-8') rescue nil
|
262
|
+
end
|
263
|
+
|
264
|
+
assert_equal([
|
265
|
+
%{COPY "foo" FROM STDIN ENCODING 'UTF-8';}
|
266
|
+
], statements)
|
267
|
+
end
|
268
|
+
|
269
|
+
def test_copy_from_program
|
270
|
+
skip unless ActiveRecord::PostgreSQLExtensions::Features.copy_from_program?
|
271
|
+
|
272
|
+
Mig.copy_from(:foo, 'cat /dev/null', :program => true) rescue nil
|
273
|
+
|
274
|
+
assert_equal([
|
275
|
+
%{COPY "foo" FROM PROGRAM 'cat /dev/null';}
|
276
|
+
], statements)
|
277
|
+
end
|
278
|
+
|
279
|
+
def test_cluster_all
|
280
|
+
ARBC.cluster_all
|
281
|
+
ARBC.cluster_all(:verbose => true)
|
282
|
+
|
283
|
+
assert_equal([
|
284
|
+
%{CLUSTER;},
|
285
|
+
%{CLUSTER VERBOSE;}
|
286
|
+
], statements)
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
def test_cluster_table
|
291
|
+
Mig.cluster(:foo)
|
292
|
+
Mig.cluster(:foo, :verbose => true)
|
293
|
+
Mig.cluster(:foo, :using => "bar_idx")
|
294
|
+
ARBC.cluster(:foo => :bar)
|
295
|
+
|
296
|
+
assert_equal([
|
297
|
+
%{CLUSTER "foo";},
|
298
|
+
%{CLUSTER VERBOSE "foo";},
|
299
|
+
%{CLUSTER "foo" USING "bar_idx";},
|
300
|
+
%{CLUSTER "foo"."bar";}
|
301
|
+
], statements)
|
302
|
+
end
|
210
303
|
end
|
data/test/constraints_tests.rb
CHANGED
@@ -2,9 +2,7 @@
|
|
2
2
|
$: << File.dirname(__FILE__)
|
3
3
|
require 'test_helper'
|
4
4
|
|
5
|
-
class ConstraintTests <
|
6
|
-
include PostgreSQLExtensionsTestHelper
|
7
|
-
|
5
|
+
class ConstraintTests < PostgreSQLExtensionsTestCase
|
8
6
|
def setup
|
9
7
|
clear_statements!
|
10
8
|
end
|
@@ -16,18 +14,24 @@ class ConstraintTests < MiniTest::Unit::TestCase
|
|
16
14
|
t.text :email
|
17
15
|
t.unique_constraint [ :id, :bar_id ]
|
18
16
|
t.unique_constraint [ :name, :email ], :tablespace => 'fubar'
|
17
|
+
t.unique_constraint :name, :storage_parameters => {
|
18
|
+
:fillfactor => 10
|
19
|
+
}
|
20
|
+
t.unique_constraint :email, :storage_parameters => 'FILLFACTOR = 10'
|
19
21
|
end
|
20
22
|
|
21
|
-
assert_equal((<<-
|
22
|
-
CREATE TABLE "foo" (
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
)
|
30
|
-
|
23
|
+
assert_equal(strip_heredoc(<<-SQL), statements[0])
|
24
|
+
CREATE TABLE "foo" (
|
25
|
+
"id" serial primary key,
|
26
|
+
"bar_id" integer,
|
27
|
+
"name" text,
|
28
|
+
"email" text,
|
29
|
+
UNIQUE ("id", "bar_id"),
|
30
|
+
UNIQUE ("name", "email") USING INDEX TABLESPACE "fubar",
|
31
|
+
UNIQUE ("name") WITH ("fillfactor" = 10),
|
32
|
+
UNIQUE ("email") WITH (FILLFACTOR = 10)
|
33
|
+
);
|
34
|
+
SQL
|
31
35
|
end
|
32
36
|
|
33
37
|
def test_create_table_with_unique_constraint_on_column
|
@@ -35,13 +39,13 @@ EOF
|
|
35
39
|
t.integer :bar_id, :unique => true
|
36
40
|
end
|
37
41
|
|
38
|
-
assert_equal((<<-
|
39
|
-
CREATE TABLE "foo" (
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
);
|
44
|
-
|
42
|
+
assert_equal(strip_heredoc(<<-SQL), statements[0])
|
43
|
+
CREATE TABLE "foo" (
|
44
|
+
"id" serial primary key,
|
45
|
+
"bar_id" integer,
|
46
|
+
UNIQUE ("bar_id")
|
47
|
+
);
|
48
|
+
SQL
|
45
49
|
end
|
46
50
|
|
47
51
|
def test_add_unique_constraint
|
@@ -55,8 +59,8 @@ EOF
|
|
55
59
|
)
|
56
60
|
|
57
61
|
assert_equal([
|
58
|
-
|
59
|
-
|
62
|
+
%{ALTER TABLE "foo" ADD UNIQUE ("bar_id");},
|
63
|
+
%{ALTER TABLE "foo" ADD CONSTRAINT "bar_id_unique" UNIQUE ("bar_id") WITH (FILLFACTOR=10) USING INDEX TABLESPACE "fubar";}
|
60
64
|
], statements)
|
61
65
|
end
|
62
66
|
|
@@ -73,16 +77,17 @@ EOF
|
|
73
77
|
t.integer :baz_id, :references => [ :baz ]
|
74
78
|
end
|
75
79
|
|
76
|
-
assert_equal([
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
);
|
80
|
+
assert_equal([ strip_heredoc(<<-SQL) ], statements)
|
81
|
+
CREATE TABLE "foo" (
|
82
|
+
"id" serial primary key,
|
83
|
+
"foo_id" integer,
|
84
|
+
"bar_id" integer,
|
85
|
+
"baz_id" integer,
|
86
|
+
FOREIGN KEY ("foo_id") REFERENCES "foo" ON DELETE SET NULL ON UPDATE CASCADE,
|
87
|
+
FOREIGN KEY ("bar_id") REFERENCES "bar",
|
88
|
+
FOREIGN KEY ("baz_id") REFERENCES "baz"
|
89
|
+
);
|
90
|
+
SQL
|
86
91
|
end
|
87
92
|
|
88
93
|
def test_foreign_key_in_table_definition
|
@@ -95,15 +100,16 @@ EOF
|
|
95
100
|
t.foreign_key [ :schabba_id, :doo_id ], :bar, [ :schabba_id, :doo_id ]
|
96
101
|
end
|
97
102
|
|
98
|
-
assert_equal([
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
);
|
103
|
+
assert_equal([ strip_heredoc(<<-SQL) ], statements)
|
104
|
+
CREATE TABLE "foo" (
|
105
|
+
"id" serial primary key,
|
106
|
+
"schabba_id" integer,
|
107
|
+
"doo_id" integer,
|
108
|
+
FOREIGN KEY ("schabba_id") REFERENCES "bar",
|
109
|
+
FOREIGN KEY ("doo_id") REFERENCES "baz",
|
110
|
+
FOREIGN KEY ("schabba_id", "doo_id") REFERENCES "bar" ("schabba_id", "doo_id")
|
111
|
+
);
|
112
|
+
SQL
|
107
113
|
end
|
108
114
|
|
109
115
|
def test_add_foreign_key
|
@@ -114,11 +120,11 @@ EOF
|
|
114
120
|
Mig.add_foreign_key(:foo, :bar_id, :bar, :deferrable => :immediate)
|
115
121
|
|
116
122
|
assert_equal([
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
123
|
+
%{ALTER TABLE "foo" ADD FOREIGN KEY ("bar_id") REFERENCES "bar";},
|
124
|
+
%{ALTER TABLE "foo" ADD CONSTRAINT "bar_fk" FOREIGN KEY ("bar_id") REFERENCES "bar" ("ogc_fid");},
|
125
|
+
%{ALTER TABLE "foo" ADD FOREIGN KEY ("one_id", "bar_id") REFERENCES "bar" ("one_id", "bar_id") MATCH FULL;},
|
126
|
+
%{ALTER TABLE "foo" ADD FOREIGN KEY ("bar_id") REFERENCES "bar" ON DELETE SET DEFAULT;},
|
127
|
+
%{ALTER TABLE "foo" ADD FOREIGN KEY ("bar_id") REFERENCES "bar" DEFERRABLE INITIALLY IMMEDIATE;}
|
122
128
|
], statements)
|
123
129
|
end
|
124
130
|
|
@@ -127,8 +133,8 @@ EOF
|
|
127
133
|
Mig.drop_constraint(:foo, :bar, :cascade => true)
|
128
134
|
|
129
135
|
assert_equal([
|
130
|
-
|
131
|
-
|
136
|
+
%{ALTER TABLE "foo" DROP CONSTRAINT "bar";},
|
137
|
+
%{ALTER TABLE "foo" DROP CONSTRAINT "bar" CASCADE;}
|
132
138
|
], statements)
|
133
139
|
end
|
134
140
|
|
@@ -142,17 +148,18 @@ EOF
|
|
142
148
|
} ]
|
143
149
|
end
|
144
150
|
|
145
|
-
assert_equal([
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
);
|
151
|
+
assert_equal([ strip_heredoc(<<-SQL) ], statements )
|
152
|
+
CREATE TABLE "foo" (
|
153
|
+
"id" serial primary key,
|
154
|
+
"foo_id" integer,
|
155
|
+
"bar_id" integer,
|
156
|
+
"baz_id" integer,
|
157
|
+
CHECK (foo_id != 1),
|
158
|
+
CONSTRAINT "bar_id_not_1" CHECK (bar_id != 1),
|
159
|
+
CHECK (baz_id != 1),
|
160
|
+
CONSTRAINT "baz_id_gt_10" CHECK (baz_id > 10)
|
161
|
+
);
|
162
|
+
SQL
|
156
163
|
end
|
157
164
|
|
158
165
|
def test_check_constraint_in_table_definition
|
@@ -169,17 +176,18 @@ EOF
|
|
169
176
|
}
|
170
177
|
end
|
171
178
|
|
172
|
-
assert_equal([
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
);
|
179
|
+
assert_equal([ strip_heredoc(<<-SQL) ], statements)
|
180
|
+
CREATE TABLE "foo" (
|
181
|
+
"id" serial primary key,
|
182
|
+
"foo_id" integer,
|
183
|
+
"bar_id" integer,
|
184
|
+
"baz_id" integer,
|
185
|
+
CHECK (foo_id != 1),
|
186
|
+
CONSTRAINT "bar_id_not_1" CHECK (bar_id != 1),
|
187
|
+
CHECK (baz_id != 1),
|
188
|
+
CONSTRAINT "baz_id_gt_10" CHECK (baz_id > 10)
|
189
|
+
);
|
190
|
+
SQL
|
183
191
|
end
|
184
192
|
|
185
193
|
def test_add_check_constraint
|
@@ -187,8 +195,8 @@ EOF
|
|
187
195
|
Mig.add_check_constraint(:foo, 'length(name) < 100', :name => 'name_length_check')
|
188
196
|
|
189
197
|
assert_equal([
|
190
|
-
|
191
|
-
|
198
|
+
%{ALTER TABLE "foo" ADD CHECK (length(name) < 100);},
|
199
|
+
%{ALTER TABLE "foo" ADD CONSTRAINT "name_length_check" CHECK (length(name) < 100);}
|
192
200
|
], statements)
|
193
201
|
end
|
194
202
|
|
@@ -235,6 +243,16 @@ EOF
|
|
235
243
|
:index_parameters => 'FILLFACTOR=10'
|
236
244
|
})
|
237
245
|
|
246
|
+
Mig.add_exclude_constraint(:foo, {
|
247
|
+
:element => 'length(name)',
|
248
|
+
:with => '='
|
249
|
+
}, {
|
250
|
+
:tablespace => 'fubar',
|
251
|
+
:index_parameters => {
|
252
|
+
:fillfactor => 10
|
253
|
+
}
|
254
|
+
})
|
255
|
+
|
238
256
|
escaped_array = if ActiveRecord::VERSION::STRING >= "3.0"
|
239
257
|
"(1, 2, 3, 4)"
|
240
258
|
else
|
@@ -247,7 +265,8 @@ EOF
|
|
247
265
|
%{ALTER TABLE "foo" ADD CONSTRAINT "exclude_name_length" EXCLUDE USING "gist" (length(name) WITH =);},
|
248
266
|
%{ALTER TABLE "foo" ADD EXCLUDE (length(name) WITH =, length(title) WITH =);},
|
249
267
|
%{ALTER TABLE "foo" ADD EXCLUDE (length(name) WITH =) WHERE ("foos"."id" IN #{escaped_array});},
|
250
|
-
%{ALTER TABLE "foo" ADD EXCLUDE (length(name) WITH =) WITH (FILLFACTOR=10) USING INDEX TABLESPACE "fubar";}
|
268
|
+
%{ALTER TABLE "foo" ADD EXCLUDE (length(name) WITH =) WITH (FILLFACTOR=10) USING INDEX TABLESPACE "fubar";},
|
269
|
+
%{ALTER TABLE "foo" ADD EXCLUDE (length(name) WITH =) WITH ("fillfactor" = 10) USING INDEX TABLESPACE "fubar";}
|
251
270
|
], statements)
|
252
271
|
end
|
253
272
|
|
@@ -263,16 +282,39 @@ EOF
|
|
263
282
|
}
|
264
283
|
end
|
265
284
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
285
|
+
Mig.create_table('foo', :id => false) do |t|
|
286
|
+
t.integer :foo_id, :primary_key => {
|
287
|
+
:tablespace => 'fubar',
|
288
|
+
:index_parameters => {
|
289
|
+
:fillfactor => 10
|
290
|
+
}
|
291
|
+
}
|
292
|
+
end
|
293
|
+
|
294
|
+
expected = []
|
295
|
+
|
296
|
+
expected << strip_heredoc(<<-SQL)
|
297
|
+
CREATE TABLE "foo" (
|
298
|
+
"foo_id" integer,
|
299
|
+
PRIMARY KEY ("foo_id")
|
300
|
+
);
|
301
|
+
SQL
|
302
|
+
|
303
|
+
expected << strip_heredoc(<<-SQL)
|
304
|
+
CREATE TABLE "foo" (
|
305
|
+
"foo_id" integer,
|
306
|
+
PRIMARY KEY ("foo_id") WITH (FILLFACTOR=10) USING INDEX TABLESPACE "fubar"
|
307
|
+
);
|
308
|
+
SQL
|
309
|
+
|
310
|
+
expected << strip_heredoc(<<-SQL)
|
311
|
+
CREATE TABLE "foo" (
|
312
|
+
"foo_id" integer,
|
313
|
+
PRIMARY KEY ("foo_id") WITH ("fillfactor" = 10) USING INDEX TABLESPACE "fubar"
|
314
|
+
);
|
315
|
+
SQL
|
316
|
+
|
317
|
+
assert_equal(expected, statements)
|
276
318
|
end
|
277
319
|
|
278
320
|
|
@@ -299,22 +341,50 @@ EOF
|
|
299
341
|
}
|
300
342
|
end
|
301
343
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
344
|
+
Mig.create_table('foo', :id => false) do |t|
|
345
|
+
t.integer :foo_id
|
346
|
+
t.integer :bar_id
|
347
|
+
t.primary_key_constraint [ :foo_id, :bar_id ], {
|
348
|
+
:tablespace => 'fubar',
|
349
|
+
:index_parameters => {
|
350
|
+
:fillfactor => 10
|
351
|
+
}
|
352
|
+
}
|
353
|
+
end
|
354
|
+
|
355
|
+
expected = []
|
356
|
+
|
357
|
+
expected << strip_heredoc(<<-SQL)
|
358
|
+
CREATE TABLE "foo" (
|
359
|
+
"foo_id" integer,
|
360
|
+
PRIMARY KEY ("foo_id")
|
361
|
+
);
|
362
|
+
SQL
|
363
|
+
|
364
|
+
expected << strip_heredoc(<<-SQL)
|
365
|
+
CREATE TABLE "foo" (
|
366
|
+
"foo_id" integer,
|
367
|
+
PRIMARY KEY ("foo_id") WITH (FILLFACTOR=10) USING INDEX TABLESPACE "fubar"
|
368
|
+
);
|
369
|
+
SQL
|
370
|
+
|
371
|
+
expected << strip_heredoc(<<-SQL)
|
372
|
+
CREATE TABLE "foo" (
|
373
|
+
"foo_id" integer,
|
374
|
+
"bar_id" integer,
|
375
|
+
PRIMARY KEY ("foo_id", "bar_id") WITH (FILLFACTOR=10) USING INDEX TABLESPACE "fubar"
|
376
|
+
);
|
377
|
+
SQL
|
378
|
+
|
379
|
+
expected << strip_heredoc(<<-SQL)
|
380
|
+
CREATE TABLE "foo" (
|
381
|
+
"foo_id" integer,
|
382
|
+
"bar_id" integer,
|
383
|
+
PRIMARY KEY ("foo_id", "bar_id") WITH ("fillfactor" = 10) USING INDEX TABLESPACE "fubar"
|
384
|
+
);
|
385
|
+
SQL
|
386
|
+
|
387
|
+
assert_equal(expected, statements)
|
318
388
|
end
|
319
389
|
|
320
390
|
def test_add_primary_key
|
@@ -322,12 +392,16 @@ EOF
|
|
322
392
|
Mig.add_primary_key(:foo, [ :bar_id, :baz_id ])
|
323
393
|
Mig.add_primary_key(:foo, :bar_id, :name => 'foo_pk')
|
324
394
|
Mig.add_primary_key(:foo, :bar_id, :tablespace => 'fubar', :index_parameters => 'FILLFACTOR=10')
|
395
|
+
Mig.add_primary_key(:foo, :bar_id, :tablespace => 'fubar', :index_parameters => {
|
396
|
+
:fillfactor => 10
|
397
|
+
})
|
325
398
|
|
326
399
|
assert_equal([
|
327
400
|
%{ALTER TABLE "foo" ADD PRIMARY KEY ("bar_id");},
|
328
401
|
%{ALTER TABLE "foo" ADD PRIMARY KEY ("bar_id", "baz_id");},
|
329
402
|
%{ALTER TABLE "foo" ADD CONSTRAINT "foo_pk" PRIMARY KEY ("bar_id");},
|
330
|
-
%{ALTER TABLE "foo" ADD PRIMARY KEY ("bar_id") WITH (FILLFACTOR=10) USING INDEX TABLESPACE "fubar";}
|
403
|
+
%{ALTER TABLE "foo" ADD PRIMARY KEY ("bar_id") WITH (FILLFACTOR=10) USING INDEX TABLESPACE "fubar";},
|
404
|
+
%{ALTER TABLE "foo" ADD PRIMARY KEY ("bar_id") WITH ("fillfactor" = 10) USING INDEX TABLESPACE "fubar";}
|
331
405
|
], statements)
|
332
406
|
end
|
333
407
|
|
@@ -336,8 +410,8 @@ EOF
|
|
336
410
|
Mig.add_foreign_key(:foo, :bar_id, :bar, :not_valid => true)
|
337
411
|
|
338
412
|
assert_equal([
|
339
|
-
|
340
|
-
|
413
|
+
%{ALTER TABLE "foo" ADD CHECK (length(name) < 100) NOT VALID;},
|
414
|
+
%{ALTER TABLE "foo" ADD FOREIGN KEY ("bar_id") REFERENCES "bar" NOT VALID;}
|
341
415
|
], statements)
|
342
416
|
end
|
343
417
|
|
@@ -345,7 +419,7 @@ EOF
|
|
345
419
|
Mig.validate_constraint(:foo, :foo_constraint)
|
346
420
|
|
347
421
|
assert_equal([
|
348
|
-
|
422
|
+
%{ALTER TABLE "foo" VALIDATE CONSTRAINT "foo_constraint";}
|
349
423
|
], statements)
|
350
424
|
end
|
351
425
|
|
@@ -353,7 +427,45 @@ EOF
|
|
353
427
|
Mig.add_check_constraint(:foo, 'length(name) < 100', :no_inherit => true)
|
354
428
|
|
355
429
|
assert_equal([
|
356
|
-
|
430
|
+
%{ALTER TABLE "foo" ADD CHECK (length(name) < 100) NO INHERIT;},
|
431
|
+
], statements)
|
432
|
+
end
|
433
|
+
|
434
|
+
def test_invalid_deferrable_option
|
435
|
+
assert_raises(ActiveRecord::InvalidDeferrableOption) do
|
436
|
+
Mig.add_foreign_key(:foo, :bar_id, :bar, :deferrable => :foo)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def test_true_deferrable_option
|
441
|
+
Mig.add_foreign_key(:foo, :bar_id, :bar, :deferrable => true)
|
442
|
+
|
443
|
+
assert_equal([
|
444
|
+
%{ALTER TABLE "foo" ADD FOREIGN KEY ("bar_id") REFERENCES "bar" DEFERRABLE;}
|
357
445
|
], statements)
|
358
446
|
end
|
447
|
+
|
448
|
+
def test_false_deferrable_option
|
449
|
+
Mig.add_foreign_key(:foo, :bar_id, :bar, :deferrable => false)
|
450
|
+
|
451
|
+
assert_equal([
|
452
|
+
%{ALTER TABLE "foo" ADD FOREIGN KEY ("bar_id") REFERENCES "bar" NOT DEFERRABLE;}
|
453
|
+
], statements)
|
454
|
+
end
|
455
|
+
|
456
|
+
def test_invalid_match_type
|
457
|
+
assert_raises(ActiveRecord::InvalidMatchType) do
|
458
|
+
Mig.add_foreign_key(:foo, :bar_id, :bar, :match => :foo)
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
def test_invalid_action_type
|
463
|
+
assert_raises(ActiveRecord::InvalidForeignKeyAction) do
|
464
|
+
Mig.add_foreign_key(:foo, :bar_id, :bar, :on_delete => :blort)
|
465
|
+
end
|
466
|
+
|
467
|
+
assert_raises(ActiveRecord::InvalidForeignKeyAction) do
|
468
|
+
Mig.add_foreign_key(:foo, :bar_id, :bar, :on_update => :blort)
|
469
|
+
end
|
470
|
+
end
|
359
471
|
end
|