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