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