activerecord-postgresql-extensions 0.0.10 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,246 @@
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_extension(name, options = {})
11
+ sql = "CREATE EXTENSION "
12
+ sql << "IF NOT EXISTS " if options[:if_not_exists]
13
+ sql << quote_generic(name)
14
+ sql << " SCHEMA #{quote_generic(options[:schema])}" if options[:schema]
15
+ sql << " VERSION #{quote_generic(options[:version])}" if options[:version]
16
+ sql << " FROM #{quote_generic(options[:old_version])}" if options[:old_version]
17
+
18
+ execute("#{sql};")
19
+ end
20
+
21
+ # ==== Options
22
+ #
23
+ # * <tt>if_exists</tt> - adds IF EXISTS.
24
+ # * <tt>cascade</tt> - adds CASCADE.
25
+ def drop_extension(*args)
26
+ options = args.extract_options!
27
+
28
+ sql = 'DROP EXTENSION '
29
+ sql << 'IF EXISTS ' if options[:if_exists]
30
+ sql << Array(args).collect { |name| quote_generic(name) }.join(', ')
31
+ sql << ' CASCADE' if options[:cascade]
32
+
33
+ execute("#{sql};")
34
+ end
35
+
36
+ def update_extension(name, new_version = nil)
37
+ sql = "ALTER EXTENSION #{quote_generic(name)} UPDATE"
38
+ sql << " TO #{quote_generic(new_version)}" if new_version;
39
+ execute("#{sql};")
40
+ end
41
+
42
+ def alter_extension_schema(name, schema)
43
+ execute "ALTER EXTENSION #{quote_generic(name)} SET SCHEMA #{quote_schema(schema)};"
44
+ end
45
+
46
+ # Alters an extension. Can be used with an options Hash or in a bloack.
47
+ # For instance, all of the following examples should produce the
48
+ # same output.
49
+ #
50
+ # # with options Hash
51
+ # alter_extension(:foo, :collation => 'en_CA.UTF-8')
52
+ # alter_extension(:foo, :add_collation => 'en_CA.UTF-8')
53
+ #
54
+ # # block mode
55
+ # alter_extension(:foo) do |e|
56
+ # e.collation 'en_CA.UTF-8'
57
+ # end
58
+ #
59
+ # alter_extension(:foo) do |e|
60
+ # e.add_collation 'en_CA.UTF-8'
61
+ # end
62
+ #
63
+ # # All produce
64
+ # #
65
+ # # ALTER EXTENSION "foo" ADD COLLATION "en_CA.UTF-8";
66
+ #
67
+ # Three versions of each option are available:
68
+ #
69
+ # * add_OPTION;
70
+ # * drop_OPTION; and
71
+ # * OPTION, which is equiavlent to add_OPTION.
72
+ #
73
+ # See the PostgreSQL docs for a list of all of the available extension
74
+ # options.
75
+ #
76
+ # ==== Per-Option, uh... Options
77
+ #
78
+ # <tt>:cast</tt>, <tt>:operator</tt>, <tt>:operator_class</tt> and
79
+ # <tt>:operator_family</tt> can be set their options as a Hash like so:
80
+ #
81
+ # # With the options Hash being the actual values:
82
+ # alter_extension(:foo, :cast => { :hello => :world })
83
+ #
84
+ # # With the options Hash containing key-values:
85
+ # alter_extension(:foo, :cast => {
86
+ # :source => :hello,
87
+ # :target => :world
88
+ # })
89
+ #
90
+ # # Or with an Array thusly:
91
+ # alter_extension(:foo, :cast => [ :source_type, :target_type ])
92
+ #
93
+ # # Or with arguments like this here:
94
+ # alter_extension(:foo) do |e|
95
+ # e.cast :source_type, :target_type
96
+ # end
97
+ #
98
+ # The options themselves even have options! It's options all the way
99
+ # down!
100
+ #
101
+ # * <tt>:aggregate</tt> - <tt>:name</tt> and <tt>:types</tt>.
102
+ #
103
+ # * <tt>:cast</tt> - <tt>:source</tt> and <tt>:target</tt>.
104
+ #
105
+ # * <tt>:function</tt> - <tt>:name</tt> and <tt>:arguments</tt>. The
106
+ # <tt>:arguments</tt> option is just a straight up String like in
107
+ # the other function manipulation methods.
108
+ #
109
+ # * <tt>:operator</tt> - <tt>:name</tt>, <tt>:left_type</tt> and
110
+ # <tt>:right_type</tt>.
111
+ #
112
+ # * <tt>:operator_class</tt> and <tt>:operator_family</tt> - <tt>:name</tt>
113
+ # and <tt>:indexing_method</tt>.
114
+ def alter_extension(name, options = {})
115
+ alterer = PostgreSQLExtensionAlterer.new(self, name, options)
116
+
117
+ if block_given?
118
+ yield alterer
119
+ end
120
+
121
+ execute(alterer.to_s) unless alterer.empty?
122
+ end
123
+ end
124
+
125
+ class PostgreSQLExtensionAlterer
126
+ def initialize(base, name, options = {}) #:nodoc:
127
+ @base, @name, @options = base, name, options
128
+ @sql = options.collect { |k, v| build_statement(k, v) }
129
+ end
130
+
131
+ def empty? #:nodoc:
132
+ @sql.empty?
133
+ end
134
+
135
+ def to_sql #:nodoc:
136
+ "#{@sql.join(";\n")};"
137
+ end
138
+ alias :to_s :to_sql
139
+
140
+ %w{
141
+ aggregate cast collation conversion domain foreign_data_wrapper
142
+ foreign_table function operator operator_class operator_family
143
+ language schema sequence server table
144
+ text_search_configuration text_search_dictionary text_search_parser text_search_template
145
+ type view
146
+ }.each do |f|
147
+ self.class_eval(<<-EOF, __FILE__, __LINE__ + 1)
148
+ def add_#{f}(*args)
149
+ @sql << build_statement(:add_#{f}, *args)
150
+ end
151
+ alias :#{f} :add_#{f}
152
+
153
+ def drop_#{f}(*args)
154
+ @sql << build_statement(:drop_#{f}, *args)
155
+ end
156
+ EOF
157
+ end
158
+
159
+ private
160
+ ACTIONS = %w{ add drop }.freeze
161
+
162
+ def build_statement(k, *args) #:nodoc:
163
+ option = k.to_s
164
+
165
+ if option =~ /^(add|drop)_/
166
+ action = $1
167
+ option = option.gsub(/^(add|drop)_/, '')
168
+ else
169
+ action = :add
170
+ end
171
+
172
+ sql = "ALTER EXTENSION #{@base.quote_generic(@name || name)} #{action.to_s.upcase} "
173
+ sql << case option
174
+ when 'aggregate'
175
+ name, types = case v = args[0]
176
+ when Hash
177
+ v.values_at(:name, :types)
178
+ else
179
+ [ args.shift, args ]
180
+ end
181
+
182
+ "AGGREGATE %s (%s)" % [
183
+ @base.quote_generic(name),
184
+ Array(types).collect { |t|
185
+ @base.quote_generic(t)
186
+ }.join(', ')
187
+ ]
188
+
189
+ when 'cast'
190
+ source, target = extract_hash_or_array_options(args, :source, :target)
191
+
192
+ "CAST (#{@base.quote_generic(source)} AS #{@base.quote_generic(target)})"
193
+ when *%w{ collation conversion domain foreign_data_wrapper
194
+ foreign_table language schema sequence server table
195
+ text_search_configuration text_search_dictionary text_search_parser
196
+ text_search_template type view }
197
+
198
+ "#{option.upcase.gsub('_', ' ')} #{@base.quote_generic(args[0])}"
199
+ when 'function'
200
+ name, arguments = case v = args[0]
201
+ when Hash
202
+ v.values_at(:name, :arguments)
203
+ else
204
+ args.flatten!
205
+ [ args.shift, *args ]
206
+ end
207
+
208
+ "FUNCTION #{@base.quote_function(name)}(#{Array(arguments).join(', ')})"
209
+ when 'operator'
210
+ name, left_type, right_type =
211
+ extract_hash_or_array_options(args, :name, :left_type, :right_type)
212
+
213
+ "OPERATOR #{@base.quote_generic(name)} (#{@base.quote_generic(left_type)}, #{@base.quote_generic(right_type)})"
214
+ when 'operator_class', 'operator_family'
215
+ object_name, indexing_method =
216
+ extract_hash_or_array_options(args, :name, :indexing_method)
217
+
218
+ "#{option.upcase.gsub('_', ' ')} #{@base.quote_generic(object_name)} USING #{@base.quote_generic(indexing_method)})"
219
+ end
220
+ sql
221
+ end
222
+
223
+ def assert_valid_action(option) #:nodoc:
224
+ if !ACTIONS.include? option.to_s.downcase
225
+ raise ArgumentError.new("Excepted :add or :drop for PostgreSQLExtensionAlterer action.")
226
+ end unless option.nil?
227
+ end
228
+
229
+ def extract_hash_or_array_options(hash_or_array, *keys) #:nodoc:
230
+ case v = hash_or_array[0]
231
+ when Hash
232
+ if (keys - (sliced = v.slice(*keys)).keys).length == 0
233
+ keys.collect do |k|
234
+ sliced[k]
235
+ end
236
+ else
237
+ [ v.keys.first, v.values.first ]
238
+ end
239
+ else
240
+ v = hash_or_array.flatten
241
+ [ v.shift, *v ]
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
@@ -91,20 +91,19 @@ module ActiveRecord
91
91
  #
92
92
  # ==== Example
93
93
  #
94
- # ### ruby
95
- # create_function('tester_function', 'integer',
96
- # 'integer', 'sql', :behavior => :immutable, :set => { :search_path => :from_current }, :force => true) do
97
- # "select $1;"
98
- # end
94
+ # create_function('tester_function', 'integer',
95
+ # 'integer', 'sql', :behavior => :immutable, :set => { :search_path => :from_current }, :force => true) do
96
+ # "select $1;"
97
+ # end
99
98
  #
100
- # # Produces
101
- # #
102
- # # CREATE OR REPLACE FUNCTION "tester_function"(integer) RETURNS integer AS $$
103
- # # select $1;
104
- # # $$
105
- # # LANGUAGE "sql"
106
- # # IMMUTABLE
107
- # # SET "search_path" FROM CURRENT;
99
+ # # Produces:
100
+ # #
101
+ # # CREATE OR REPLACE FUNCTION "tester_function"(integer) RETURNS integer AS $$
102
+ # # select $1;
103
+ # # $$
104
+ # # LANGUAGE "sql"
105
+ # # IMMUTABLE
106
+ # # SET "search_path" FROM CURRENT;
108
107
  def create_function(name, args, returns, language, options = {})
109
108
  body = yield.to_s
110
109
  execute PostgreSQLFunctionDefinition.new(self, name, args, returns, language, body, options).to_s
@@ -122,7 +121,7 @@ module ActiveRecord
122
121
  sql << 'IF EXISTS ' if options[:if_exists]
123
122
  sql << "#{quote_function(name)}(#{args})"
124
123
  sql << ' CASCADE' if options[:cascade]
125
- execute sql
124
+ execute "#{sql};"
126
125
  end
127
126
 
128
127
  # Renames a function.
@@ -155,29 +154,28 @@ module ActiveRecord
155
154
  #
156
155
  # Both of the following examples should produce the same output.
157
156
  #
158
- # ### ruby
159
- # # with options Hash
160
- # alter_function('my_function', 'integer', :rename_to => 'another_function')
161
- # alter_function('another_function', 'integer', :owner_to => 'jdoe')
157
+ # # with options Hash
158
+ # alter_function('my_function', 'integer', :rename_to => 'another_function')
159
+ # alter_function('another_function', 'integer', :owner_to => 'jdoe')
162
160
  #
163
- # # block mode
164
- # alter_function('my_function', 'integer') do |f|
165
- # f.rename_to 'another_function'
166
- # f.owner_to 'jdoe'
167
- # end
161
+ # # block mode
162
+ # alter_function('my_function', 'integer') do |f|
163
+ # f.rename_to 'another_function'
164
+ # f.owner_to 'jdoe'
165
+ # end
168
166
  #
169
- # # Produces
170
- # #
171
- # # ALTER FUNCTION "my_function"(integer) OWNER TO "jdoe";
172
- # # ALTER FUNCTION "my_function"(integer) RENAME TO "another_function";
167
+ # # Produces:
168
+ # #
169
+ # # ALTER FUNCTION "my_function"(integer) OWNER TO "jdoe";
170
+ # # ALTER FUNCTION "my_function"(integer) RENAME TO "another_function";
173
171
  def alter_function(name, args, options = {})
172
+ alterer = PostgreSQLFunctionAlterer.new(self, name, args, options)
173
+
174
174
  if block_given?
175
- alterer = PostgreSQLFunctionAlterer.new(self, name, args)
176
175
  yield alterer
177
- execute alterer.to_s unless alterer.empty?
178
- else
179
- execute PostgreSQLFunctionAlterer.new(self, name, args, options).to_s
180
176
  end
177
+
178
+ execute alterer.to_s unless alterer.empty?
181
179
  end
182
180
  end
183
181
 
@@ -271,7 +269,7 @@ module ActiveRecord
271
269
  sql << " COST #{options[:cost].to_i}\n" if options[:cost]
272
270
  sql << " ROWS #{options[:rows].to_i}\n" if options[:rows]
273
271
  sql << " " << (set_options(options[:set]) * "\n ") if options[:set]
274
- sql.strip
272
+ "#{sql.strip};"
275
273
  end
276
274
  alias :to_s :to_sql
277
275
  end
@@ -291,7 +289,7 @@ module ActiveRecord
291
289
  end
292
290
 
293
291
  def to_sql #:nodoc:
294
- @sql.join(";\n")
292
+ "#{@sql.join(";\n")};"
295
293
  end
296
294
  alias :to_s :to_sql
297
295
 
@@ -127,14 +127,14 @@ module ActiveRecord
127
127
  "DELETE FROM \"geometry_columns\" WHERE f_table_catalog = '' AND " +
128
128
  "f_table_schema = %s AND " +
129
129
  "f_table_name = %s AND " +
130
- "f_geometry_column = %s",
130
+ "f_geometry_column = %s;",
131
131
  base.quote(current_schema.to_s),
132
132
  base.quote(current_table_name.to_s),
133
133
  base.quote(column_name.to_s)
134
134
  )
135
135
 
136
136
  @post_processing << sprintf(
137
- "INSERT INTO \"geometry_columns\" VALUES ('', %s, %s, %s, %d, %d, %s)",
137
+ "INSERT INTO \"geometry_columns\" VALUES ('', %s, %s, %s, %d, %d, %s);",
138
138
  base.quote(current_schema.to_s),
139
139
  base.quote(current_table_name.to_s),
140
140
  base.quote(column_name.to_s),
@@ -93,18 +93,17 @@ module ActiveRecord
93
93
  #
94
94
  # ==== Examples
95
95
  #
96
- # ### ruby
97
- # # using multiple columns
98
- # create_index('this_is_my_index', :foo, [ :id, :ref_id ], :using => :gin)
99
- # # => CREATE INDEX "this_is_my_index" ON "foo"("id", "ref_id");
96
+ # # using multiple columns
97
+ # create_index('this_is_my_index', :foo, [ :id, :ref_id ], :using => :gin)
98
+ # # => CREATE INDEX "this_is_my_index" ON "foo"("id", "ref_id");
100
99
  #
101
- # # using expressions
102
- # create_index('this_is_another_idx', :foo, { :expression => 'COALESCE(ref_id, 0)' })
103
- # # => CREATE INDEX "this_is_another_idx" ON "foo"((COALESCE(ref_id, 0)));
100
+ # # using expressions
101
+ # create_index('this_is_another_idx', :foo, { :expression => 'COALESCE(ref_id, 0)' })
102
+ # # => CREATE INDEX "this_is_another_idx" ON "foo"((COALESCE(ref_id, 0)));
104
103
  #
105
- # # additional options
106
- # create_index('search_idx', :foo, :tsvector, :using => :gin)
107
- # # => CREATE INDEX "search_idx" ON "foo" USING "gin"("tsvector");
104
+ # # additional options
105
+ # create_index('search_idx', :foo, :tsvector, :using => :gin)
106
+ # # => CREATE INDEX "search_idx" ON "foo" USING "gin"("tsvector");
108
107
  def create_index(name, table, columns, options = {})
109
108
  execute PostgreSQLIndexDefinition.new(self, name, table, columns, options).to_s
110
109
  end
@@ -125,17 +124,17 @@ module ActiveRecord
125
124
  sql << 'IF EXISTS ' if options[:if_exists]
126
125
  sql << Array(name).collect { |i| quote_generic(i) }.join(', ')
127
126
  sql << ' CASCADE' if options[:cascade]
128
- execute sql
127
+ execute("#{sql};")
129
128
  end
130
129
 
131
130
  # Renames an index.
132
131
  def rename_index(name, new_name, options = {})
133
- execute "ALTER INDEX #{quote_generic(name)} RENAME TO #{quote_generic(new_name)}"
132
+ execute "ALTER INDEX #{quote_generic(name)} RENAME TO #{quote_generic(new_name)};"
134
133
  end
135
134
 
136
135
  # Changes an index's tablespace.
137
136
  def alter_index_tablespace(name, tablespace, options = {})
138
- execute "ALTER INDEX #{quote_generic(name)} SET TABLESPACE #{quote_tablespace(tablespace)}"
137
+ execute "ALTER INDEX #{quote_generic(name)} SET TABLESPACE #{quote_tablespace(tablespace)};"
139
138
  end
140
139
  end
141
140
 
@@ -181,7 +180,7 @@ module ActiveRecord
181
180
  sql << " WITH (FILLFACTOR = #{options[:fill_factor].to_i})" if options[:fill_factor]
182
181
  sql << " TABLESPACE #{base.quote_tablespace(options[:tablespace])}" if options[:tablespace]
183
182
  sql << " WHERE #{options[:conditions] || options[:where]}" if options[:conditions] || options[:where]
184
- sql
183
+ "#{sql};"
185
184
  end
186
185
  alias :to_s :to_sql
187
186
 
@@ -37,7 +37,7 @@ module ActiveRecord
37
37
  sql << "PROCEDURAL LANGUAGE #{quote_language(language)}"
38
38
  sql << " HANDLER #{quote_language(options[:call_handler])}" if options[:call_handler]
39
39
  sql << " VALIDATOR #{options[:validator]}" if options[:validator]
40
- execute sql
40
+ execute("#{sql};")
41
41
  end
42
42
 
43
43
  # Drops a language.
@@ -51,17 +51,17 @@ module ActiveRecord
51
51
  sql << 'IF EXISTS ' if options[:if_exists]
52
52
  sql << quote_language(language)
53
53
  sql << ' CASCADE' if options[:cascade]
54
- execute sql
54
+ execute("#{sql};")
55
55
  end
56
56
 
57
57
  # Renames a language.
58
58
  def alter_language_name old_language, new_language, options = {}
59
- execute "ALTER PROCEDURAL LANGUAGE #{quote_language(old_language)} RENAME TO #{quote_language(new_language)}"
59
+ execute "ALTER PROCEDURAL LANGUAGE #{quote_language(old_language)} RENAME TO #{quote_language(new_language)};"
60
60
  end
61
61
 
62
62
  # Changes a language's owner.
63
63
  def alter_language_owner language, role, options = {}
64
- execute "ALTER PROCEDURAL LANGUAGE #{quote_language(language)} OWNER TO #{quote_language(role)}"
64
+ execute "ALTER PROCEDURAL LANGUAGE #{quote_language(language)} OWNER TO #{quote_language(role)};"
65
65
  end
66
66
 
67
67
  # Returns an Array of available languages.