pg_power 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -169,7 +169,15 @@ expressions are supported.
169
169
  Add an index to a column with a function
170
170
 
171
171
  ```ruby
172
- add_index(:comments, "lower(text)")
172
+ add_index(:comments, "lower(text)")
173
+ ```
174
+
175
+ You can also specify index access method
176
+
177
+ ```ruby
178
+ create_extension 'btree_gist'
179
+ create_extension 'fuzzystrmatch'
180
+ add_index(:comments, 'dmetaphone(author)', :using => 'gist')
173
181
  ```
174
182
 
175
183
  ## Concurrent index creation
@@ -191,6 +199,39 @@ Add an index concurrently along with foreign key
191
199
  add_foreign_key :table1, :table2, :column => :column_id, :concurrent_index => true
192
200
  ```
193
201
 
202
+ ## Loading/Unloading postgresql extension modules
203
+
204
+ Postgresql is shipped with a number of [extension modules](http://www.postgresql.org/docs/9.1/static/contrib.html).
205
+ PgPower provides some tools
206
+ to [load](http://www.postgresql.org/docs/9.1/static/sql-createextension.html)/[unload](http://www.postgresql.org/docs/9.1/static/sql-dropextension.html)
207
+ such modules by the means of migrations.
208
+
209
+ Please note. CREATE/DROP EXTENSION command has been introduced in postgresql 9.1 only. So this functionality will not be
210
+ available for the previous versions.
211
+
212
+ ### Examples
213
+
214
+ Load [fuzzystrmatch](http://www.postgresql.org/docs/9.1/static/fuzzystrmatch.html) extension module
215
+ and create its objects in schema *public*:
216
+
217
+ ```ruby
218
+ create_extension "fuzzystrmatch"
219
+ ```
220
+
221
+
222
+ Load version *1.0* of the [btree_gist](http://www.postgresql.org/docs/9.1/static/btree-gist.html) extension module
223
+ and create its objects in schema *demography*.
224
+
225
+ ```ruby
226
+ create_extension "btree_gist", :schema_name => "demography", :version => "1.0"
227
+ ```
228
+
229
+ Unload extension module:
230
+
231
+ ```ruby
232
+ drop_extension "fuzzystrmatch"
233
+ ```
234
+
194
235
  ## Tools
195
236
 
196
237
  PgPower::Tools provides number of useful methods:
@@ -203,18 +244,13 @@ PgPower::Tools.index_exists?(table, columns, options) # => returns true if an
203
244
  ```
204
245
  ## Running tests:
205
246
 
247
+ * Ensure your postgresql has postgres-contrib (Ubuntu) package installed. Tests depend on btree_gist and fuzzystrmatch extensions
206
248
  * Configure `spec/dummy/config/database.yml` for development and test environments.
207
249
  * Run `rake spec`.
208
250
  * Make sure migrations don't raise exceptions and all specs pass.
209
251
 
210
252
  ## TODO:
211
253
 
212
- Add next syntax to create table:
213
- ```ruby
214
- create_table "table_name", :schema => "schema_name" do |t|
215
- # columns goes here
216
- end
217
- ```
218
254
  Support for JRuby:
219
255
 
220
256
  * Jdbc driver provides its own `create_schema(schema, user)` method - solve conflicts.
@@ -224,6 +260,7 @@ Support for JRuby:
224
260
  * [Potapov Sergey](https://github.com/greyblake) - schema support
225
261
  * [Arthur Shagall](https://github.com/albertosaurus) - thanks for [pg_comment](https://github.com/albertosaurus/pg_comment)
226
262
  * [Matthew Higgins](https://github.com/matthuhiggins) - thanks for [foreigner](https://github.com/matthuhiggins/foreigner), which was used as a base for the foreign key support
263
+ * [Artem Ignatyev](https://github.com/cryo28) - extension modules load/unload support
227
264
  * [Marcelo Silveira](https://github.com/mhfs) - thanks for rails partial index support that was backported into this gem
228
265
 
229
266
  ## Copyright and License
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  # an Array of Symbols.
10
10
  #
11
11
  # ====== Creating a partial index
12
- # add_index(:accounts, [:branch_id, :party_id],
12
+ # add_index(:accounts, [:branch_id, :party_id], :using => 'BTree'
13
13
  # :unique => true, :concurrently => true, :where => 'active')
14
14
  # generates
15
15
  # CREATE UNIQUE INDEX CONCURRENTLY
@@ -38,8 +38,14 @@ module ActiveRecord
38
38
  "column can not be created concurrently, because such index already exists."
39
39
  end
40
40
 
41
- execute "CREATE #{type} INDEX #{creation_method} #{quote_column_name(name)} " \
42
- "ON #{quote_table_name(table_name)} (#{columns})#{opts}"
41
+ sql = ["CREATE #{type} INDEX"]
42
+ sql << creation_method.to_s
43
+ sql << quote_column_name(name)
44
+ sql << "ON #{quote_table_name(table_name)}"
45
+ sql << "USING #{options[:using].to_s.downcase}" if options[:using]
46
+ sql << "(#{columns})#{opts}"
47
+
48
+ execute sql.join(" ")
43
49
  end
44
50
 
45
51
  # Checks to see if an index exists on a table for a given index definition.
@@ -42,10 +42,11 @@ module ActiveRecord # :nodoc:
42
42
  schemas = schema ? "ARRAY['#{schema}']" : 'current_schemas(false)'
43
43
 
44
44
  result = query(<<-SQL, name)
45
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
45
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid, am.amname
46
46
  FROM pg_class t
47
47
  INNER JOIN pg_index d ON t.oid = d.indrelid
48
48
  INNER JOIN pg_class i ON d.indexrelid = i.oid
49
+ INNER JOIN pg_am am ON i.relam = am.oid
49
50
  WHERE i.relkind = 'i'
50
51
  AND d.indisprimary = 'f'
51
52
  AND t.relname = '#{table}'
@@ -55,11 +56,12 @@ module ActiveRecord # :nodoc:
55
56
 
56
57
  result.map do |row|
57
58
  index = {
58
- :name => row[0],
59
- :unique => row[1] == 't',
60
- :keys => row[2].split(" "),
61
- :definition => row[3],
62
- :id => row[4]
59
+ :name => row[0],
60
+ :unique => row[1] == 't',
61
+ :keys => row[2].split(" "),
62
+ :definition => row[3],
63
+ :id => row[4],
64
+ :access_method => row[5]
63
65
  }
64
66
 
65
67
  column_names = find_column_names(table_name, index)
@@ -68,7 +70,7 @@ module ActiveRecord # :nodoc:
68
70
  where = find_where_statement(index)
69
71
  lengths = find_lengths(index)
70
72
 
71
- PgPower::ConnectionAdapters::IndexDefinition.new(table_name, index[:name], index[:unique], column_names, lengths, where)
73
+ PgPower::ConnectionAdapters::IndexDefinition.new(table_name, index[:name], index[:unique], column_names, lengths, where, index[:access_method])
72
74
  end
73
75
  end.compact
74
76
  end
@@ -29,6 +29,8 @@ module ActiveRecord #:nodoc:
29
29
  # Append :where clause if a partial index
30
30
  statement_parts << (':where => ' + index.where.inspect) if index.where
31
31
 
32
+ statement_parts << (':using => ' + index.access_method.inspect) unless index.access_method.downcase == 'btree'
33
+
32
34
  ' ' + statement_parts.join(', ')
33
35
  end
34
36
 
@@ -1,6 +1,6 @@
1
1
  module PgPower::ConnectionAdapters
2
2
  # Structure to store index parameters
3
3
  # Overrides ActiveRecord::ConnectionAdapters::IndexDefinition with the additional :where parameter
4
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :where)
4
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :where, :access_method)
5
5
  end
6
6
  end
@@ -5,12 +5,14 @@ module PgPower::ConnectionAdapters::PostgreSQLAdapter
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  # TODO: Looks like explicit path specification can be omitted -- aignatyev 20120904
8
+ autoload :ExtensionMethods, 'pg_power/connection_adapters/postgresql_adapter/extension_methods'
8
9
  autoload :SchemaMethods, 'pg_power/connection_adapters/postgresql_adapter/schema_methods'
9
10
  autoload :CommentMethods, 'pg_power/connection_adapters/postgresql_adapter/comment_methods'
10
11
  autoload :ForeignerMethods, 'pg_power/connection_adapters/postgresql_adapter/foreigner_methods'
11
12
  autoload :IndexMethods, 'pg_power/connection_adapters/postgresql_adapter/index_methods'
12
13
  autoload :TranslateException, 'pg_power/connection_adapters/postgresql_adapter/translate_exception'
13
14
 
15
+ include ExtensionMethods
14
16
  include SchemaMethods
15
17
  include CommentMethods
16
18
  include ForeignerMethods
@@ -0,0 +1,118 @@
1
+ # Provides methods to extend {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter}
2
+ # to support extensions feature.
3
+ module PgPower::ConnectionAdapters::PostgreSQLAdapter::ExtensionMethods
4
+ # Default options for {#create_extension} method
5
+ CREATE_EXTENSION_DEFAULTS = {
6
+ :if_not_exists => true,
7
+ :schema_name => nil,
8
+ :version => nil,
9
+ :old_version => nil
10
+ }
11
+
12
+ # Default options for {#drop_extension} method
13
+ DROP_EXTENSION_DEFAULTS = {
14
+ :if_exists => true,
15
+ :mode => :restrict
16
+ }
17
+
18
+ # The modes which determine postgresql behavior on DROP EXTENSION operation.
19
+ #
20
+ # *:restrict* refuse to drop the extension if any objects depend on it
21
+ # *:cascade* automatically drop objects that depend on the extension
22
+ AVAILABLE_DROP_MODES = {
23
+ :restrict => 'RESTRICT',
24
+ :cascade => 'CASCADE'
25
+ }
26
+
27
+ # @return [Boolean] if adapter supports postgresql extension manipulation
28
+ def supports_extensions?
29
+ true
30
+ end
31
+
32
+ # Executes SQL to load a postgresql extension module into the current database
33
+ #
34
+ # @param [#to_s] extension_name Name of the extension module to load
35
+ # @param [Hash] options
36
+ # @option options [Boolean] :if_not_exists should the 'IF NOT EXISTS' clause be added
37
+ # @option options [#to_s,nil] :schema_name The name of the schema in which to install the extension's objects
38
+ # @option options [#to_s,nil] :version The version of the extension to install
39
+ # @option options [#to_s,nil] :old_version Alternative installation script name
40
+ # that absorbs the existing objects into the extension, instead of creating new objects
41
+ def create_extension(extension_name, options = {})
42
+ options = CREATE_EXTENSION_DEFAULTS.merge(options.symbolize_keys)
43
+ sql = ['CREATE EXTENSION']
44
+ sql << 'IF NOT EXISTS' if options[:if_not_exists]
45
+ sql << extension_name.to_s
46
+ sql << "SCHEMA #{options[:schema_name]}" if options[:schema_name].present?
47
+ sql << "VERSION '#{options[:version]}'" if options[:version].present?
48
+ sql << "FROM #{options[:old_version]}" if options[:old_version].present?
49
+
50
+ sql = sql.join(' ')
51
+ execute(sql)
52
+ end
53
+
54
+
55
+ # Executes SQL to remove a postgresql extension module from the current database
56
+ #
57
+ # @param [#to_s] extension_name Name of the extension module to unload
58
+ # @param [Hash] options
59
+ # @option options [Boolean] :if_exists should the 'IF EXISTS' clause be added
60
+ # @option options [Symbol] :mode Operation mode. See {AVAILABLE_DROP_MODES} for details
61
+ def drop_extension(extension_name, options = {})
62
+ options = DROP_EXTENSION_DEFAULTS.merge(options.symbolize_keys)
63
+
64
+ sql = ['DROP EXTENSION']
65
+ sql << 'IF EXISTS' if options[:if_exists]
66
+ sql << extension_name.to_s
67
+
68
+ mode = options[:mode]
69
+ if mode.present?
70
+ mode = mode.to_sym
71
+
72
+ unless AVAILABLE_DROP_MODES.include?(mode)
73
+ raise ArgumentError, "Expected one of #{AVAILABLE_DROP_MODES.KEYS.inspect} drop modes, but #{mode} received"
74
+ end
75
+
76
+ sql << AVAILABLE_DROP_MODES[mode]
77
+ end
78
+
79
+ sql = sql.join(' ')
80
+ execute(sql)
81
+ end
82
+
83
+ # Queries pg_catalog for all loaded to the current database extension modules
84
+ #
85
+ # Please note all extensions which belong to pg_catalog schema are omitted
86
+ # ===Example
87
+ #
88
+ # extension # => {
89
+ # "fuzzystrmatch" => {:schema_name => "public", :version => "1.0" }
90
+ # }
91
+ #
92
+ # @return [Hash{String => Hash{Symbol => String}}] A list of loaded extensions with their options
93
+ def extensions
94
+ # Check postgresql version to not break on Postgresql < 9.1 during schema dump
95
+ pg_version_str = select_value('SELECT version()')
96
+ return {} unless pg_version_str =~ /^PostgreSQL (\d+\.\d+.\d+)/ && ($1 >= '9.1')
97
+
98
+ sql = <<-SQL
99
+ SELECT pge.extname AS ext_name, pgn.nspname AS schema_name, pge.extversion AS ext_version
100
+ FROM pg_extension pge
101
+ INNER JOIN pg_namespace pgn on pge.extnamespace = pgn.oid
102
+ WHERE pgn.nspname <> 'pg_catalog'
103
+ SQL
104
+
105
+ result = select_all(sql)
106
+ result.map! do |row|
107
+ [
108
+ row['ext_name'],
109
+ {
110
+ :schema_name => row['schema_name'],
111
+ :version => row['ext_version']
112
+ }
113
+ ]
114
+ end
115
+
116
+ Hash[result]
117
+ end
118
+ end
@@ -3,10 +3,12 @@
3
3
  module PgPower::Migration::CommandRecorder
4
4
  extend ActiveSupport::Autoload
5
5
 
6
+ autoload :ExtensionMethods
6
7
  autoload :SchemaMethods
7
8
  autoload :CommentMethods
8
9
  autoload :ForeignerMethods
9
10
 
11
+ include ExtensionMethods
10
12
  include SchemaMethods
11
13
  include CommentMethods
12
14
  include ForeignerMethods
@@ -0,0 +1,25 @@
1
+ # Provides methods to extend {ActiveRecord::Migration::CommandRecorder} to
2
+ # support extensions feature.
3
+ module PgPower::Migration::CommandRecorder::ExtensionMethods
4
+ # :nodoc:
5
+ def create_extension(*args)
6
+ record(:create_extension, args)
7
+ end
8
+
9
+ # :nodoc:
10
+ def drop_extension(*args)
11
+ record(:drop_extension, args)
12
+ end
13
+
14
+ # :nodoc:
15
+ def invert_create_extension(args)
16
+ extension_name = args.first
17
+ [:drop_extension, [extension_name]]
18
+ end
19
+
20
+ # :nodoc:
21
+ def invert_drop_extension(args)
22
+ extension_name = args.first
23
+ [:create_extension, [extension_name]]
24
+ end
25
+ end
@@ -5,15 +5,20 @@ module PgPower::SchemaDumper
5
5
  extend ActiveSupport::Autoload
6
6
  extend ActiveSupport::Concern
7
7
 
8
+ autoload :ExtensionMethods
8
9
  autoload :CommentMethods
9
10
  autoload :SchemaMethods
10
11
  autoload :ForeignerMethods
11
12
 
13
+ include ExtensionMethods
12
14
  include CommentMethods
13
15
  include SchemaMethods
14
16
  include ForeignerMethods
15
17
 
16
18
  included do
19
+ alias_method_chain :header, :schemas
20
+ alias_method_chain :header, :extensions
21
+
17
22
  alias_method_chain :tables, :schemas
18
23
  alias_method_chain :tables, :comments
19
24
  alias_method_chain :tables, :foreign_keys
@@ -0,0 +1,28 @@
1
+ # Extends ActiveRecord::SchemaDumper class to dump comments on tables and columns.
2
+ module PgPower::SchemaDumper::ExtensionMethods
3
+ # Hooks {ActiveRecord::SchemaDumper#header} method to dump extensions in all schemas except for pg_catalog
4
+ def header_with_extensions(stream)
5
+ header_without_extensions(stream)
6
+ dump_extensions(stream)
7
+ stream
8
+ end
9
+
10
+ # Dump current database extensions recreation commands to the given stream
11
+ #
12
+ # @param [#puts] stream Stream to write to
13
+ def dump_extensions(stream)
14
+ extensions = @connection.extensions
15
+ commands = extensions.map do |extension_name, options|
16
+ result = [%Q|create_extension "#{extension_name}"|]
17
+ result << %Q|:schema_name => "#{options[:schema_name]}"| unless options[:schema_name] == 'public'
18
+ result << %Q|:version => "#{options[:version]}"|
19
+ result.join(', ')
20
+ end
21
+
22
+ commands.each do |c|
23
+ stream.puts(" " + c)
24
+ end
25
+
26
+ stream.puts
27
+ end
28
+ end
@@ -1,11 +1,17 @@
1
1
  # Extends ActiveRecord::SchemaDumper class to dump schemas other than "public"
2
2
  # and tables from those schemas.
3
3
  module PgPower::SchemaDumper::SchemaMethods
4
+ # Dump create schema statements
5
+ def header_with_schemas(stream)
6
+ header_without_schemas(stream)
7
+ schemas(stream)
8
+ stream
9
+ end
10
+
4
11
  # * Dumps schemas.
5
12
  # * Dumps tables from public schema using native #tables method.
6
13
  # * Dumps tables from schemas other than public.
7
14
  def tables_with_schemas(stream)
8
- schemas(stream)
9
15
  tables_without_schemas(stream)
10
16
  non_public_schema_tables(stream)
11
17
  end
@@ -1,4 +1,4 @@
1
1
  module PgPower
2
2
  # Version of pg_power gem.
3
- VERSION = "1.1.0"
3
+ VERSION = "1.3.0"
4
4
  end
metadata CHANGED
@@ -1,17 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_power
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Potapov Sergey
9
9
  - Arthur Shagall
10
+ - Artem Ignatyev
10
11
  - TMX Credit
11
12
  autorequire:
12
13
  bindir: bin
13
14
  cert_chain: []
14
- date: 2012-09-26 00:00:00.000000000 Z
15
+ date: 2012-11-06 00:00:00.000000000 Z
15
16
  dependencies:
16
17
  - !ruby/object:Gem::Dependency
17
18
  name: pg
@@ -141,8 +142,8 @@ dependencies:
141
142
  - - ! '>='
142
143
  - !ruby/object:Gem::Version
143
144
  version: '0'
144
- description: ActiveRecord extensions for PostgreSQL. Provides useful tools and ability
145
- to create/drop schemas in migrations.
145
+ description: ActiveRecord extensions for PostgreSQL. Provides useful tools for schema,
146
+ foreign_key, index, comment and extensios manipulations in migrations.
146
147
  email:
147
148
  - rubygems@tmxcredit.com
148
149
  executables: []
@@ -166,6 +167,7 @@ files:
166
167
  - lib/pg_power/connection_adapters/index_definition.rb
167
168
  - lib/pg_power/connection_adapters/postgresql_adapter.rb
168
169
  - lib/pg_power/connection_adapters/postgresql_adapter/comment_methods.rb
170
+ - lib/pg_power/connection_adapters/postgresql_adapter/extension_methods.rb
169
171
  - lib/pg_power/connection_adapters/postgresql_adapter/foreigner_methods.rb
170
172
  - lib/pg_power/connection_adapters/postgresql_adapter/index_methods.rb
171
173
  - lib/pg_power/connection_adapters/postgresql_adapter/schema_methods.rb
@@ -179,10 +181,12 @@ files:
179
181
  - lib/pg_power/migration.rb
180
182
  - lib/pg_power/migration/command_recorder.rb
181
183
  - lib/pg_power/migration/command_recorder/comment_methods.rb
184
+ - lib/pg_power/migration/command_recorder/extension_methods.rb
182
185
  - lib/pg_power/migration/command_recorder/foreigner_methods.rb
183
186
  - lib/pg_power/migration/command_recorder/schema_methods.rb
184
187
  - lib/pg_power/schema_dumper.rb
185
188
  - lib/pg_power/schema_dumper/comment_methods.rb
189
+ - lib/pg_power/schema_dumper/extension_methods.rb
186
190
  - lib/pg_power/schema_dumper/foreigner_methods.rb
187
191
  - lib/pg_power/schema_dumper/schema_methods.rb
188
192
  - lib/pg_power/tools.rb
@@ -202,7 +206,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
202
206
  version: '0'
203
207
  segments:
204
208
  - 0
205
- hash: -621483421206137072
209
+ hash: 3962762731367048390
206
210
  required_rubygems_version: !ruby/object:Gem::Requirement
207
211
  none: false
208
212
  requirements: