activerecord-postgresql-extensions 0.0.10 → 0.0.11

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 (37) hide show
  1. data/Rakefile +2 -2
  2. data/VERSION +1 -1
  3. data/activerecord-postgresql-extensions.gemspec +20 -17
  4. data/lib/activerecord-postgresql-extensions.rb +2 -0
  5. data/lib/postgresql_extensions/postgresql_adapter_extensions.rb +50 -53
  6. data/lib/postgresql_extensions/postgresql_constraints.rb +142 -153
  7. data/lib/postgresql_extensions/postgresql_extensions.rb +246 -0
  8. data/lib/postgresql_extensions/postgresql_functions.rb +31 -33
  9. data/lib/postgresql_extensions/postgresql_geometry.rb +2 -2
  10. data/lib/postgresql_extensions/postgresql_indexes.rb +13 -14
  11. data/lib/postgresql_extensions/postgresql_languages.rb +4 -4
  12. data/lib/postgresql_extensions/postgresql_permissions.rb +12 -14
  13. data/lib/postgresql_extensions/postgresql_roles.rb +2 -2
  14. data/lib/postgresql_extensions/postgresql_rules.rb +11 -10
  15. data/lib/postgresql_extensions/postgresql_schemas.rb +4 -4
  16. data/lib/postgresql_extensions/postgresql_sequences.rb +15 -16
  17. data/lib/postgresql_extensions/postgresql_tables.rb +20 -21
  18. data/lib/postgresql_extensions/postgresql_text_search.rb +313 -0
  19. data/lib/postgresql_extensions/postgresql_triggers.rb +13 -14
  20. data/lib/postgresql_extensions/postgresql_types.rb +1 -1
  21. data/lib/postgresql_extensions/postgresql_views.rb +13 -14
  22. data/test/{adapter_test.rb → adapter_tests.rb} +6 -6
  23. data/test/{constraints_test.rb → constraints_tests.rb} +13 -13
  24. data/test/extensions_tests.rb +275 -0
  25. data/test/{functions_test.rb → functions_tests.rb} +10 -10
  26. data/test/{geometry_test.rb → geometry_tests.rb} +16 -16
  27. data/test/{index_test.rb → index_tests.rb} +11 -11
  28. data/test/{languages_test.rb → languages_tests.rb} +6 -6
  29. data/test/{permissions_test.rb → permissions_tests.rb} +36 -36
  30. data/test/{roles_test.rb → roles_tests.rb} +6 -6
  31. data/test/{rules_test.rb → rules_tests.rb} +3 -3
  32. data/test/{schemas_test.rb → schemas_tests.rb} +6 -6
  33. data/test/{sequences_test.rb → sequences_tests.rb} +10 -10
  34. data/test/{tables_test.rb → tables_tests.rb} +2 -2
  35. data/test/text_search_tests.rb +263 -0
  36. metadata +19 -16
  37. data/postgresql-extensions.gemspec +0 -50
@@ -80,7 +80,7 @@ module ActiveRecord
80
80
  sql << ' TO '
81
81
  sql << Array(role_names).collect { |r| quote_role(r) }.join(', ')
82
82
  sql << ' WITH ADMIN OPTION' if options[:with_admin_option]
83
- execute sql
83
+ execute("#{sql};")
84
84
  end
85
85
 
86
86
  # Revokes table privileges. You can specify multiple tables,
@@ -155,7 +155,7 @@ module ActiveRecord
155
155
  sql << ' FROM '
156
156
  sql << Array(role_names).collect { |r| quote_role(r) }.join(', ')
157
157
  sql << ' CASCADE' if options[:cascade]
158
- execute sql
158
+ execute("#{sql};")
159
159
  end
160
160
  end
161
161
 
@@ -204,12 +204,11 @@ module ActiveRecord
204
204
  #
205
205
  # ==== Examples
206
206
  #
207
- # ### ruby
208
- # grant_table_privileges([ :table1, :table2 ], :select, :joe)
209
- # # => GRANT SELECT ON TABLE "table1", "table2" TO "joe"
207
+ # grant_table_privileges([ :table1, :table2 ], :select, :joe)
208
+ # # => GRANT SELECT ON TABLE "table1", "table2" TO "joe"
210
209
  #
211
- # grant_sequence_privileges(:my_seq, [ :select, :update ], :public)
212
- # # => GRANT SELECT, UPDATE ON SEQUENCE "my_seq" TO PUBLIC
210
+ # grant_sequence_privileges(:my_seq, [ :select, :update ], :public)
211
+ # # => GRANT SELECT, UPDATE ON SEQUENCE "my_seq" TO PUBLIC
213
212
  #
214
213
  # You can specify the <tt>:with_grant_option</tt> in any of the
215
214
  # grant_*_privilege methods to add a WITH GRANT OPTION clause to
@@ -244,7 +243,7 @@ module ActiveRecord
244
243
  end.join(', ')
245
244
 
246
245
  sql << ' WITH GRANT OPTION' if options[:with_grant_option]
247
- sql
246
+ "#{sql};"
248
247
  end
249
248
  alias :to_s :to_sql
250
249
  end
@@ -264,12 +263,11 @@ module ActiveRecord
264
263
  #
265
264
  # ==== Examples
266
265
  #
267
- # ### ruby
268
- # revoke_table_privileges([ :table1, :table2 ], :select, :joe)
269
- # # => REVOKE SELECT ON TABLE "table1", "table2" FROM "joe"
266
+ # revoke_table_privileges([ :table1, :table2 ], :select, :joe)
267
+ # # => REVOKE SELECT ON TABLE "table1", "table2" FROM "joe"
270
268
  #
271
- # revoke_sequence_privileges(:my_seq, [ :select, :update ], :public)
272
- # # => REVOKE SELECT, UPDATE ON SEQUENCE "my_seq" FROM PUBLIC
269
+ # revoke_sequence_privileges(:my_seq, [ :select, :update ], :public)
270
+ # # => REVOKE SELECT, UPDATE ON SEQUENCE "my_seq" FROM PUBLIC
273
271
  #
274
272
  # You can specify the <tt>:grant_option_for</tt> in any of the
275
273
  # revoke_*_privilege methods to add a GRANT OPTION FOR clause to
@@ -314,7 +312,7 @@ module ActiveRecord
314
312
  end.join(', ')
315
313
 
316
314
  sql << ' CASCADE' if options[:cascade]
317
- sql
315
+ "#{sql};"
318
316
  end
319
317
  alias :to_s :to_sql
320
318
  end
@@ -24,7 +24,7 @@ module ActiveRecord
24
24
  sql = 'DROP ROLE '
25
25
  sql << 'IF EXISTS ' if options[:if_exists]
26
26
  sql << Array(name).collect { |r| quote_role(r) }.join(', ')
27
- execute sql
27
+ execute("#{sql};")
28
28
  end
29
29
  alias :drop_user :drop_role
30
30
  end
@@ -112,7 +112,7 @@ module ActiveRecord
112
112
  sql << Array(options[:admin]).collect { |r| base.quote_role(r) }.join(', ')
113
113
  end
114
114
 
115
- sql.join(' ')
115
+ "#{sql.join(' ')};"
116
116
  end
117
117
  alias :to_s :to_sql
118
118
 
@@ -37,22 +37,21 @@ module ActiveRecord
37
37
  #
38
38
  # ==== Examples
39
39
  #
40
- # ### ruby
41
- # create_rule(
42
- # 'check_it_out_rule',
43
- # :select,
44
- # :child,
45
- # :instead,
46
- # 'select * from public.another', :conditions => 'id = 1'
47
- # )
48
- # # => CREATE RULE "check_it_out_rule" AS ON SELECT TO "child" WHERE id = 1 DO INSTEAD select * from public.another;
40
+ # create_rule(
41
+ # 'check_it_out_rule',
42
+ # :select,
43
+ # :child,
44
+ # :instead,
45
+ # 'select * from public.another', :conditions => 'id = 1'
46
+ # )
47
+ # # => CREATE RULE "check_it_out_rule" AS ON SELECT TO "child" WHERE id = 1 DO INSTEAD select * from public.another;
49
48
  def create_rule(name, event, table, action, commands, options = {})
50
49
  execute PostgreSQLRuleDefinition.new(self, name, event, table, action, commands, options).to_s
51
50
  end
52
51
 
53
52
  # Drops a PostgreSQL rule.
54
53
  def drop_rule(name, table)
55
- execute "DROP RULE #{quote_rule(name)} ON #{quote_table_name(table)}"
54
+ execute "DROP RULE #{quote_rule(name)} ON #{quote_table_name(table)};"
56
55
  end
57
56
  end
58
57
 
@@ -89,6 +88,8 @@ module ActiveRecord
89
88
  else
90
89
  commands.to_s
91
90
  end
91
+
92
+ "#{sql};"
92
93
  end
93
94
  alias :to_s :to_sql
94
95
 
@@ -18,7 +18,7 @@ module ActiveRecord
18
18
  def create_schema schema, options = {}
19
19
  sql = "CREATE SCHEMA #{quote_schema(schema)}"
20
20
  sql << " AUTHORIZATION #{quote_role(options[:authorization])}" if options[:authorization]
21
- execute sql
21
+ execute("#{sql};")
22
22
  end
23
23
 
24
24
  # Drops a schema.
@@ -32,17 +32,17 @@ module ActiveRecord
32
32
  sql << 'IF EXISTS ' if options[:if_exists]
33
33
  sql << Array(schemas).collect { |s| quote_schema(s) }.join(', ')
34
34
  sql << ' CASCADE' if options[:cascade]
35
- execute sql
35
+ execute("#{sql};")
36
36
  end
37
37
 
38
38
  # Alter's a schema's name.
39
39
  def alter_schema_name old_schema, new_schema
40
- execute "ALTER SCHEMA #{quote_schema(old_schema)} RENAME TO #{quote_schema(new_schema)}"
40
+ execute "ALTER SCHEMA #{quote_schema(old_schema)} RENAME TO #{quote_schema(new_schema)};"
41
41
  end
42
42
 
43
43
  # Changes a schema's owner.
44
44
  def alter_schema_owner schema, role
45
- execute "ALTER SCHEMA #{quote_schema(schema)} OWNER TO #{quote_schema(role)}"
45
+ execute "ALTER SCHEMA #{quote_schema(schema)} OWNER TO #{quote_role(role)};"
46
46
  end
47
47
  end
48
48
  end
@@ -42,17 +42,16 @@ module ActiveRecord
42
42
  #
43
43
  # ==== Example
44
44
  #
45
- # ### ruby
46
- # create_sequence(
47
- # 'what_a_sequence_of_events',
48
- # :increment => 2,
49
- # :cache => 2,
50
- # :min_value => nil,
51
- # :max_value => 10,
52
- # :owned_by => [ :foo, :id ]
53
- # )
54
- # # => CREATE SEQUENCE "what_a_sequence_of_events" INCREMENT BY 2
55
- # # NO MINVALUE MAXVALUE 10 CACHE 2 OWNED BY "foo"."id";
45
+ # create_sequence(
46
+ # 'what_a_sequence_of_events',
47
+ # :increment => 2,
48
+ # :cache => 2,
49
+ # :min_value => nil,
50
+ # :max_value => 10,
51
+ # :owned_by => [ :foo, :id ]
52
+ # )
53
+ # # => CREATE SEQUENCE "what_a_sequence_of_events" INCREMENT BY 2
54
+ # # NO MINVALUE MAXVALUE 10 CACHE 2 OWNED BY "foo"."id";
56
55
  def create_sequence(name, options = {})
57
56
  execute PostgreSQLSequenceDefinition.new(self, :create, name, options).to_s
58
57
  end
@@ -69,17 +68,17 @@ module ActiveRecord
69
68
  sql << 'IF EXISTS ' if options[:if_exists]
70
69
  sql << Array(name).collect { |s| quote_sequence(s) }.join(', ')
71
70
  sql << ' CASCADE' if options[:cascade]
72
- execute sql
71
+ execute("#{sql};")
73
72
  end
74
73
 
75
74
  # Renames the sequence.
76
75
  def rename_sequence(name, rename, options = {})
77
- execute "ALTER SEQUENCE #{quote_sequence(name)} RENAME TO #{quote_generic_ignore_schema(rename)}"
76
+ execute "ALTER SEQUENCE #{quote_sequence(name)} RENAME TO #{quote_generic_ignore_schema(rename)};"
78
77
  end
79
78
 
80
79
  # Alters the sequence's schema.
81
80
  def alter_sequence_schema(name, schema, options = {})
82
- execute "ALTER SEQUENCE #{quote_sequence(name)} SET SCHEMA #{quote_schema(schema)}"
81
+ execute "ALTER SEQUENCE #{quote_sequence(name)} SET SCHEMA #{quote_schema(schema)};"
83
82
  end
84
83
 
85
84
  # Alters any of the various options for a sequence. See
@@ -112,7 +111,7 @@ module ActiveRecord
112
111
  else
113
112
  'false'
114
113
  end <<
115
- ')'
114
+ ');'
116
115
  end
117
116
 
118
117
  # Returns an Array of available sequences.
@@ -193,7 +192,7 @@ module ActiveRecord
193
192
  if action != :create && options.has_key?(:restart_with)
194
193
  sql << "RESTART WITH #{options[:restart_with].to_i}"
195
194
  end
196
- sql.join(' ')
195
+ "#{sql.join(' ')};"
197
196
  end
198
197
  alias :to_s :to_sql
199
198
 
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  class PostgreSQLAdapter < AbstractAdapter
14
14
  # Set the schema of a table.
15
15
  def alter_table_schema table_name, schema, options = {}
16
- execute "ALTER TABLE #{quote_schema(table_name)} SET SCHEMA #{quote_schema(schema)}"
16
+ execute "ALTER TABLE #{quote_schema(table_name)} SET SCHEMA #{quote_schema(schema)};"
17
17
  end
18
18
 
19
19
  alias :original_create_table :create_table
@@ -69,24 +69,23 @@ module ActiveRecord
69
69
  #
70
70
  # ==== Examples
71
71
  #
72
- # ### ruby
73
- # create_table(:foo, :inherits => :parent) do |t|
74
- # t.integer :bar_id, :references => :bar
75
- # t.like :base, :including => [ :defaults, :indexes ], :excluding => :constraints
76
- # t.check_constraint "bar_id < 100"
77
- # t.unique_constraint :bar_id
78
- # end
72
+ # create_table(:foo, :inherits => :parent) do |t|
73
+ # t.integer :bar_id, :references => :bar
74
+ # t.like :base, :including => [ :defaults, :indexes ], :excluding => :constraints
75
+ # t.check_constraint "bar_id < 100"
76
+ # t.unique_constraint :bar_id
77
+ # end
79
78
  #
80
- # # Produces:
81
- # #
82
- # # CREATE TABLE "foo" (
83
- # # "id" serial primary key,
84
- # # "bar_id" integer DEFAULT NULL NULL,
85
- # # LIKE "base" INCLUDING DEFAULTS INCLUDING INDEXES EXCLUDING CONSTRAINTS,
86
- # # FOREIGN KEY ("bar_id") REFERENCES "bar",
87
- # # CHECK (bar_id < 100),
88
- # # UNIQUE ("bar_id")
89
- # # ) INHERITS ("parent");
79
+ # # Produces:
80
+ # #
81
+ # # CREATE TABLE "foo" (
82
+ # # "id" serial primary key,
83
+ # # "bar_id" integer DEFAULT NULL NULL,
84
+ # # LIKE "base" INCLUDING DEFAULTS INCLUDING INDEXES EXCLUDING CONSTRAINTS,
85
+ # # FOREIGN KEY ("bar_id") REFERENCES "bar",
86
+ # # CHECK (bar_id < 100),
87
+ # # UNIQUE ("bar_id")
88
+ # # ) INHERITS ("parent");
90
89
  #
91
90
  # This is a fairly convoluted example, but there you have it.
92
91
  #
@@ -131,7 +130,7 @@ module ActiveRecord
131
130
  sql << 'IF EXISTS ' if options[:if_exists]
132
131
  sql << Array(tables).collect { |t| quote_table_name(t) }.join(', ')
133
132
  sql << ' CASCADE' if options[:cascade]
134
- execute sql
133
+ execute("#{sql};")
135
134
  end
136
135
 
137
136
  alias :original_rename_table :rename_table
@@ -140,7 +139,7 @@ module ActiveRecord
140
139
  # capabilities. You can still access the original method via
141
140
  # original_rename_table.
142
141
  def rename_table(name, new_name, options = {})
143
- execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_generic_ignore_schema(new_name)}"
142
+ execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_generic_ignore_schema(new_name)};"
144
143
  end
145
144
 
146
145
  private
@@ -195,7 +194,7 @@ module ActiveRecord
195
194
  sql << "ON COMMIT #{options[:on_commit].to_s.upcase}" if options[:on_commit]
196
195
  sql << "#{options[:options]}" if options[:options]
197
196
  sql << "TABLESPACE #{base.quote_tablespace(options[:tablespace])}" if options[:tablespace]
198
- sql
197
+ "#{sql};"
199
198
  end
200
199
  alias :to_s :to_sql
201
200
 
@@ -0,0 +1,313 @@
1
+
2
+ require 'active_record/connection_adapters/postgresql_adapter'
3
+
4
+ module ActiveRecord
5
+ module ConnectionAdapters
6
+ class PostgreSQLAdapter < AbstractAdapter
7
+ # Creates a new PostgreSQL text search configuration. You must provide
8
+ # either a :parser_name or a :source_config option as per the PostgreSQL
9
+ # text search docs.
10
+ def create_text_search_configuration(name, options = {})
11
+ if options[:parser_name] && options[:source_config]
12
+ raise ArgumentError.new("You can't define both :parser_name and :source_config options.")
13
+ elsif options[:parser_name].blank? && options[:source_config].blank?
14
+ raise ArgumentError.new("You must provide either a :parser_name or a :source_config.")
15
+ end
16
+
17
+ sql = "CREATE TEXT SEARCH CONFIGURATION #{quote_generic_with_schema(name)} ("
18
+
19
+ ignore_schema do
20
+ sql << if options[:parser_name]
21
+ "PARSER = #{quote_generic_with_schema(options[:parser_name])}"
22
+ else
23
+ "COPY = #{quote_generic_with_schema(options[:source_config])}"
24
+ end
25
+ end
26
+
27
+ sql << ")"
28
+ execute("#{sql};")
29
+ end
30
+
31
+ def add_text_search_configuration_mapping(name, tokens, dictionaries)
32
+ add_or_alter_text_search_configuration_mapping(name, tokens, dictionaries, :action => :add)
33
+ end
34
+
35
+ def alter_text_search_configuration_mapping(name, tokens, dictionaries)
36
+ add_or_alter_text_search_configuration_mapping(name, tokens, dictionaries, :action => :alter)
37
+ end
38
+
39
+ # This method is semi-private and should only really be used via
40
+ # add_text_search_configuration_mapping and alter_text_search_configuration_mapping.
41
+ #
42
+ # ==== Options
43
+ #
44
+ # * <tt>:action</tt> - either :add or :alter.
45
+ def add_or_alter_text_search_configuration_mapping(name, tokens, dictionaries, options = {})
46
+ options = {
47
+ :action => :add
48
+ }.merge(options)
49
+
50
+ if ![ :add, :alter ].include?(options[:action])
51
+ raise ArgumentError.new(":action option must be eithe :add or :alter.")
52
+ end
53
+
54
+ add_or_alter = options[:action].to_s.upcase
55
+
56
+ sql = "ALTER TEXT SEARCH CONFIGURATION #{quote_generic_with_schema(name)} #{add_or_alter} MAPPING FOR "
57
+ sql << Array(tokens).collect { |token|
58
+ quote_generic(token)
59
+ }.join(', ')
60
+
61
+ sql << ' WITH '
62
+
63
+ sql << Array(dictionaries).collect { |dictionary|
64
+ quote_generic(dictionary)
65
+ }.join(', ')
66
+
67
+ execute("#{sql};")
68
+ end
69
+
70
+ def replace_text_search_configuration_dictionary(name, old_dictionary, new_dictionary)
71
+ sql = "ALTER TEXT SEARCH CONFIGURATION #{quote_generic_with_schema(name)} ALTER MAPPING REPLACE "
72
+ sql << "#{quote_generic(old_dictionary)} WITH #{quote_generic(new_dictionary)}"
73
+
74
+ execute("#{sql};")
75
+ end
76
+
77
+ def alter_text_search_configuration_mapping_replace_dictionary(name, mappings, old_dictionary, new_dictionary)
78
+ if mappings.blank?
79
+ raise ArgumentError.new("Expected one or more mappings to alter.")
80
+ end
81
+
82
+ sql = "ALTER TEXT SEARCH CONFIGURATION #{quote_generic_with_schema(name)} ALTER MAPPING FOR "
83
+ sql << Array(mappings).collect { |token_type|
84
+ quote_generic(token_type)
85
+ }.join(', ')
86
+ sql << " REPLACE #{quote_generic(old_dictionary)} WITH #{quote_generic(new_dictionary)}"
87
+
88
+ execute("#{sql};")
89
+ end
90
+
91
+ def drop_text_search_configuration_mapping(name, *args)
92
+ options = args.extract_options!
93
+ mappings = args
94
+
95
+ if mappings.blank?
96
+ raise ArgumentError.new("Expected one or more mappings to drop.")
97
+ end
98
+
99
+ sql = "ALTER TEXT SEARCH CONFIGURATION #{quote_generic_with_schema(name)} DROP MAPPING "
100
+
101
+ if options[:if_exists]
102
+ sql << 'IF EXISTS '
103
+ end
104
+
105
+ sql << 'FOR '
106
+ sql << mappings.collect { |token_type|
107
+ quote_generic(token_type)
108
+ }.join(', ')
109
+
110
+ execute("#{sql};")
111
+ end
112
+
113
+ def rename_text_search_configuration(old_name, new_name)
114
+ execute("ALTER TEXT SEARCH CONFIGURATION %s RENAME TO %s;" % [
115
+ quote_generic_with_schema(old_name),
116
+ quote_generic_with_schema(new_name)
117
+ ])
118
+ end
119
+
120
+ def alter_text_search_configuration_owner(name, role)
121
+ execute "ALTER TEXT SEARCH CONFIGURATION #{quote_generic_with_schema(name)} OWNER TO #{quote_role(role)};"
122
+ end
123
+
124
+ def alter_text_search_configuration_schema(name, schema)
125
+ execute "ALTER TEXT SEARCH CONFIGURATION #{quote_generic_with_schema(name)} SET SCHEMA #{quote_schema(schema)};"
126
+ end
127
+
128
+ # Drops a text search configuration.
129
+ #
130
+ # ==== Options
131
+ #
132
+ # * <tt>:if_exists</tt> - adds IF EXISTS.
133
+ # * <tt>:cascade</tt> - adds CASCADE.
134
+ def drop_text_search_configuration(name, options = {})
135
+ sql = 'DROP TEXT SEARCH CONFIGURATION '
136
+ sql << 'IF EXISTS ' if options[:if_exists]
137
+ sql << quote_generic_with_schema(name)
138
+ sql << ' CASCADE' if options[:cascade]
139
+
140
+ execute("#{sql};")
141
+ end
142
+
143
+ def create_text_search_dictionary(name, template, options = {})
144
+ sql = "CREATE TEXT SEARCH DICTIONARY #{quote_generic_with_schema(name)} ("
145
+ sql << "TEMPLATE = #{quote_generic_with_schema(template)}"
146
+
147
+ if !options.blank?
148
+ sql << ', '
149
+ sql << options.collect { |k, v|
150
+ "#{quote_generic(k)} = #{quote(v)}"
151
+ }.join(', ')
152
+ end
153
+
154
+ sql << ')'
155
+
156
+ execute("#{sql};")
157
+ end
158
+
159
+ # ==== Options
160
+ #
161
+ # * <tt>:if_exists</tt> - adds IF EXISTS.
162
+ # * <tt>:cascade</tt> - adds CASCADE.
163
+ def drop_text_search_dictionary(name, options = {})
164
+ sql = 'DROP TEXT SEARCH DICTIONARY '
165
+ sql << 'IF EXISTS ' if options[:if_exists]
166
+ sql << quote_generic_with_schema(name)
167
+ sql << ' CASCADE' if options[:cascade]
168
+
169
+ execute("#{sql};")
170
+ end
171
+
172
+ def alter_text_search_dictionary(name, options)
173
+ if options.blank?
174
+ raise ArgumentError.new("Expected some options to alter.")
175
+ end
176
+
177
+ sql = "ALTER TEXT SEARCH DICTIONARY #{quote_generic_with_schema} ("
178
+ sql << options.collect { |k, v|
179
+ "#{quote_generic(k)} = #{quote(v)}"
180
+ }.join(', ')
181
+ sql << ')'
182
+
183
+ execute("#{sql};")
184
+ end
185
+
186
+ def rename_text_search_dictionary(old_name, new_name)
187
+ execute("ALTER TEXT SEARCH DICTIONARY %s RENAME TO %s;" % [
188
+ quote_generic_with_schema(old_name),
189
+ quote_generic_with_schema(new_name)
190
+ ])
191
+ end
192
+
193
+ def alter_text_search_dictionary_owner(name, role)
194
+ execute "ALTER TEXT SEARCH DICTIONARY #{quote_generic_with_schema(name)} OWNER TO #{quote_role(role)};"
195
+ end
196
+
197
+ def alter_text_search_dictionary_schema(name, schema)
198
+ execute "ALTER TEXT SEARCH DICTIONARY #{quote_generic_with_schema(name)} SET SCHEMA #{quote_schema(schema)};"
199
+ end
200
+
201
+
202
+ # ==== Options
203
+ #
204
+ # :lexize - the function used by the template lexer. Required.
205
+ # :init - the initialization function for the template. Optional.
206
+ def create_text_search_template(name, options = {})
207
+ if options[:lexize].blank?
208
+ raise ArgumentError.new("Expected to see a :lexize option.")
209
+ end
210
+
211
+ sql = "CREATE TEXT SEARCH TEMPLATE #{quote_generic_with_schema(name)} ("
212
+
213
+ if options[:init]
214
+ sql << "INIT = #{quote_function(options[:init])}, "
215
+ end
216
+
217
+ sql << "LEXIZE = #{quote_function(options[:lexize])}"
218
+ sql << ')'
219
+
220
+ execute("#{sql};")
221
+ end
222
+
223
+ # ==== Options
224
+ #
225
+ # * <tt>:if_exists</tt> - adds IF EXISTS.
226
+ # * <tt>:cascade</tt> - adds CASCADE.
227
+ def drop_text_search_template(name, options = {})
228
+ sql = 'DROP TEXT SEARCH TEMPLATE '
229
+ sql << 'IF EXISTS ' if options[:if_exists]
230
+ sql << quote_generic_with_schema(name)
231
+ sql << ' CASCADE' if options[:cascade]
232
+
233
+ execute("#{sql};")
234
+ end
235
+
236
+ def rename_text_search_template(old_name, new_name)
237
+ execute("ALTER TEXT SEARCH TEMPLATE %s RENAME TO %s;" % [
238
+ quote_generic_with_schema(old_name),
239
+ quote_generic_with_schema(new_name)
240
+ ])
241
+ end
242
+
243
+ def alter_text_search_template_schema(name, schema)
244
+ execute "ALTER TEXT SEARCH TEMPLATE #{quote_generic_with_schema(name)} SET SCHEMA #{quote_schema(schema)};"
245
+ end
246
+
247
+
248
+
249
+ # The :start, :gettoken, :end and :lextypes options are required as per
250
+ # the PostgreSQL docs, while the :headline option is optional.
251
+ def create_text_search_parser(name, options = {})
252
+ if (missing_options = [ :start, :gettoken, :end, :lextypes ] - options.keys).present?
253
+ raise ArgumentError.new("Missing options: #{missing_options}.")
254
+ end
255
+
256
+ sql = "CREATE TEXT SEARCH PARSER #{quote_generic_with_schema(name)} ("
257
+ sql << "START = #{quote_function(options[:start])}, "
258
+ sql << "GETTOKEN = #{quote_function(options[:gettoken])}, "
259
+ sql << "END = #{quote_function(options[:end])}, "
260
+ sql << "LEXTYPES = #{quote_function(options[:lextypes])}"
261
+
262
+ if options[:headline]
263
+ sql << ", HEADLINE = #{quote_function(options[:headline])}"
264
+ end
265
+
266
+ sql << ')'
267
+
268
+ execute("#{sql};")
269
+ end
270
+
271
+ # ==== Options
272
+ #
273
+ # * <tt>:if_exists</tt> - adds IF EXISTS.
274
+ # * <tt>:cascade</tt> - adds CASCADE.
275
+ def drop_text_search_parser(name, options = {})
276
+ sql = 'DROP TEXT SEARCH PARSER '
277
+ sql << 'IF EXISTS ' if options[:if_exists]
278
+ sql << quote_generic_with_schema(name)
279
+ sql << ' CASCADE' if options[:cascade]
280
+
281
+ execute("#{sql};")
282
+ end
283
+
284
+ def rename_text_search_parser(old_name, new_name)
285
+ execute("ALTER TEXT SEARCH PARSER %s RENAME TO %s;" % [
286
+ quote_generic_with_schema(old_name),
287
+ quote_generic_with_schema(new_name)
288
+ ])
289
+ end
290
+
291
+ def alter_text_search_parser_schema(name, schema)
292
+ execute "ALTER TEXT SEARCH PARSER #{quote_generic_with_schema(name)} SET SCHEMA #{quote_schema(schema)};"
293
+ end
294
+
295
+ private
296
+ def extract_hash_or_array_options(hash_or_array, *keys)
297
+ case v = hash_or_array[0]
298
+ when Hash
299
+ if (keys - (sliced = v.slice(*keys)).keys).length == 0
300
+ keys.collect do |k|
301
+ sliced[k]
302
+ end
303
+ else
304
+ [ v.keys.first, v.values.first ]
305
+ end
306
+ else
307
+ v = hash_or_array.flatten
308
+ [ v.shift, *v ]
309
+ end
310
+ end
311
+ end
312
+ end
313
+ end