activerecord-postgresql-extensions 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +4 -0
  3. data/Gemfile +1 -0
  4. data/Guardfile +3 -3
  5. data/MIT-LICENSE +1 -1
  6. data/README.rdoc +10 -3
  7. data/lib/active_record/postgresql_extensions/adapter_extensions.rb +100 -60
  8. data/lib/active_record/postgresql_extensions/constraints.rb +13 -17
  9. data/lib/active_record/postgresql_extensions/event_triggers.rb +129 -0
  10. data/lib/active_record/postgresql_extensions/extensions.rb +14 -15
  11. data/lib/active_record/postgresql_extensions/features.rb +80 -41
  12. data/lib/active_record/postgresql_extensions/functions.rb +1 -1
  13. data/lib/active_record/postgresql_extensions/geometry.rb +6 -8
  14. data/lib/active_record/postgresql_extensions/indexes.rb +19 -11
  15. data/lib/active_record/postgresql_extensions/languages.rb +1 -1
  16. data/lib/active_record/postgresql_extensions/materialized_views.rb +272 -0
  17. data/lib/active_record/postgresql_extensions/permissions.rb +60 -22
  18. data/lib/active_record/postgresql_extensions/roles.rb +18 -7
  19. data/lib/active_record/postgresql_extensions/rules.rb +5 -0
  20. data/lib/active_record/postgresql_extensions/schemas.rb +39 -3
  21. data/lib/active_record/postgresql_extensions/sequences.rb +6 -3
  22. data/lib/active_record/postgresql_extensions/tables.rb +47 -19
  23. data/lib/active_record/postgresql_extensions/tablespaces.rb +1 -1
  24. data/lib/active_record/postgresql_extensions/text_search.rb +3 -3
  25. data/lib/active_record/postgresql_extensions/triggers.rb +3 -3
  26. data/lib/active_record/postgresql_extensions/types.rb +104 -1
  27. data/lib/active_record/postgresql_extensions/utils.rb +35 -13
  28. data/lib/active_record/postgresql_extensions/vacuum.rb +1 -1
  29. data/lib/active_record/postgresql_extensions/version.rb +1 -1
  30. data/lib/active_record/postgresql_extensions/views.rb +137 -6
  31. data/lib/activerecord-postgresql-extensions.rb +13 -11
  32. data/test/{adapter_tests.rb → adapter_extensions_tests.rb} +96 -3
  33. data/test/constraints_tests.rb +216 -104
  34. data/test/event_triggers_tests.rb +109 -0
  35. data/test/extensions_tests.rb +47 -39
  36. data/test/functions_tests.rb +47 -38
  37. data/test/geometry_tests.rb +268 -135
  38. data/test/{index_tests.rb → indexes_tests.rb} +16 -16
  39. data/test/languages_tests.rb +26 -9
  40. data/test/materialized_views_tests.rb +174 -0
  41. data/test/permissions_tests.rb +159 -45
  42. data/test/roles_tests.rb +17 -7
  43. data/test/rules_tests.rb +14 -6
  44. data/test/schemas_tests.rb +35 -9
  45. data/test/sequences_tests.rb +9 -11
  46. data/test/tables_tests.rb +132 -42
  47. data/test/tablespace_tests.rb +21 -15
  48. data/test/test_helper.rb +56 -10
  49. data/test/text_search_tests.rb +42 -44
  50. data/test/trigger_tests.rb +1 -3
  51. data/test/types_tests.rb +95 -0
  52. data/test/vacuum_tests.rb +1 -3
  53. data/test/views_tests.rb +203 -0
  54. metadata +22 -16
@@ -2,22 +2,44 @@
2
2
  module ActiveRecord
3
3
  module PostgreSQLExtensions
4
4
  module Utils
5
- class << self
6
- def hash_or_array_of_hashes(arg)
7
- case arg
8
- when Hash
9
- [ arg ]
10
- when Array
11
- if arg.detect { |e| !e.is_a?(Hash) }
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
- raise ArgumentError.new("Expected either a Hash or an Array of Hashes")
18
- end
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
@@ -1,7 +1,7 @@
1
1
 
2
2
  module ActiveRecord
3
3
  module PostgreSQLExtensions
4
- VERSION = "0.2.2"
4
+ VERSION = "0.3.0"
5
5
  end
6
6
  end
7
7
 
@@ -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(name, options = {})
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(name).collect { |v| quote_view_name(v) }.join(', ')
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 "ALTER TABLE #{quote_view_name(name)} RENAME TO #{quote_generic_ignore_scoped_schema(new_name)};"
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 "ALTER TABLE #{quote_view_name(name)} OWNER TO #{quote_role(role)};"
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 "ALTER TABLE #{quote_view_name(name)} SET SCHEMA #{quote_schema(schema)};"
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
- tables
26
- tablespaces
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
- functions
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 < MiniTest::Unit::TestCase
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
@@ -2,9 +2,7 @@
2
2
  $: << File.dirname(__FILE__)
3
3
  require 'test_helper'
4
4
 
5
- class ConstraintTests < MiniTest::Unit::TestCase
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((<<-EOF).strip, statements[0])
22
- CREATE TABLE "foo" (
23
- "id" serial primary key,
24
- "bar_id" integer,
25
- "name" text,
26
- "email" text,
27
- UNIQUE ("id", "bar_id"),
28
- UNIQUE ("name", "email") USING INDEX TABLESPACE "fubar"
29
- );
30
- EOF
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((<<-EOF).strip, statements[0])
39
- CREATE TABLE "foo" (
40
- "id" serial primary key,
41
- "bar_id" integer,
42
- UNIQUE ("bar_id")
43
- );
44
- EOF
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
- "ALTER TABLE \"foo\" ADD UNIQUE (\"bar_id\");",
59
- "ALTER TABLE \"foo\" ADD CONSTRAINT \"bar_id_unique\" UNIQUE (\"bar_id\") WITH (FILLFACTOR=10) USING INDEX TABLESPACE \"fubar\";"
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
- %{CREATE TABLE "foo" (
78
- "id" serial primary key,
79
- "foo_id" integer,
80
- "bar_id" integer,
81
- "baz_id" integer,
82
- FOREIGN KEY ("foo_id") REFERENCES "foo" ON DELETE SET NULL ON UPDATE CASCADE,
83
- FOREIGN KEY ("bar_id") REFERENCES "bar",
84
- FOREIGN KEY ("baz_id") REFERENCES "baz"
85
- );} ], statements)
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
- %{CREATE TABLE "foo" (
100
- "id" serial primary key,
101
- "schabba_id" integer,
102
- "doo_id" integer,
103
- FOREIGN KEY ("schabba_id") REFERENCES "bar",
104
- FOREIGN KEY ("doo_id") REFERENCES "baz",
105
- FOREIGN KEY ("schabba_id", "doo_id") REFERENCES "bar" ("schabba_id", "doo_id")
106
- );} ], statements)
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
- "ALTER TABLE \"foo\" ADD FOREIGN KEY (\"bar_id\") REFERENCES \"bar\";",
118
- "ALTER TABLE \"foo\" ADD CONSTRAINT \"bar_fk\" FOREIGN KEY (\"bar_id\") REFERENCES \"bar\" (\"ogc_fid\");",
119
- "ALTER TABLE \"foo\" ADD FOREIGN KEY (\"one_id\", \"bar_id\") REFERENCES \"bar\" (\"one_id\", \"bar_id\") MATCH FULL;",
120
- "ALTER TABLE \"foo\" ADD FOREIGN KEY (\"bar_id\") REFERENCES \"bar\" ON DELETE SET DEFAULT;",
121
- "ALTER TABLE \"foo\" ADD FOREIGN KEY (\"bar_id\") REFERENCES \"bar\" DEFERRABLE INITIALLY IMMEDIATE;"
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
- "ALTER TABLE \"foo\" DROP CONSTRAINT \"bar\";",
131
- "ALTER TABLE \"foo\" DROP CONSTRAINT \"bar\" CASCADE;"
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
- %{CREATE TABLE "foo" (
147
- "id" serial primary key,
148
- "foo_id" integer,
149
- "bar_id" integer,
150
- "baz_id" integer,
151
- CHECK (foo_id != 1),
152
- CONSTRAINT "bar_id_not_1" CHECK (bar_id != 1),
153
- CHECK (baz_id != 1),
154
- CONSTRAINT "baz_id_gt_10" CHECK (baz_id > 10)
155
- );} ], statements)
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
- %{CREATE TABLE "foo" (
174
- "id" serial primary key,
175
- "foo_id" integer,
176
- "bar_id" integer,
177
- "baz_id" integer,
178
- CHECK (foo_id != 1),
179
- CONSTRAINT "bar_id_not_1" CHECK (bar_id != 1),
180
- CHECK (baz_id != 1),
181
- CONSTRAINT "baz_id_gt_10" CHECK (baz_id > 10)
182
- );} ], statements)
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
- "ALTER TABLE \"foo\" ADD CHECK (length(name) < 100);",
191
- "ALTER TABLE \"foo\" ADD CONSTRAINT \"name_length_check\" CHECK (length(name) < 100);"
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
- assert_equal([
267
- %{CREATE TABLE "foo" (
268
- "foo_id" integer,
269
- PRIMARY KEY ("foo_id")
270
- );},
271
-
272
- %{CREATE TABLE "foo" (
273
- "foo_id" integer,
274
- PRIMARY KEY ("foo_id") WITH (FILLFACTOR=10) USING INDEX TABLESPACE "fubar"
275
- );}], statements)
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
- assert_equal([
303
- %{CREATE TABLE "foo" (
304
- "foo_id" integer,
305
- PRIMARY KEY ("foo_id")
306
- );},
307
-
308
- %{CREATE TABLE "foo" (
309
- "foo_id" integer,
310
- PRIMARY KEY ("foo_id") WITH (FILLFACTOR=10) USING INDEX TABLESPACE "fubar"
311
- );},
312
-
313
- %{CREATE TABLE "foo" (
314
- "foo_id" integer,
315
- "bar_id" integer,
316
- PRIMARY KEY ("foo_id", "bar_id") WITH (FILLFACTOR=10) USING INDEX TABLESPACE "fubar"
317
- );}], statements)
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
- "ALTER TABLE \"foo\" ADD CHECK (length(name) < 100) NOT VALID;",
340
- "ALTER TABLE \"foo\" ADD FOREIGN KEY (\"bar_id\") REFERENCES \"bar\" NOT VALID;"
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
- "ALTER TABLE \"foo\" VALIDATE CONSTRAINT \"foo_constraint\";"
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
- "ALTER TABLE \"foo\" ADD CHECK (length(name) < 100) NO INHERIT;",
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