pg_power 1.1.0 → 1.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.
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: