pg_saurus 3.7.1 → 4.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac3e7bc1ed09c54ead47c936f659d1976deca96b498347b748de3411969bbd84
4
- data.tar.gz: 1c0dbd26312b9cbde30f6fd7a659fa6760b3d7800f092828d9e81542fa5ceb2c
3
+ metadata.gz: 5b32de47fc9d97bfc956d5e2e26c9601ed2da3866dea9a51b97722c90d4b1db7
4
+ data.tar.gz: 497db5f622d6332efa57216e13527b71a2895125856330e320166f342e088637
5
5
  SHA512:
6
- metadata.gz: 5b2fdd24acf0bbe74f4607f9d189fb90054c4f92092508a390323c0f320d8364f74638b0ff64f358a287ccc20bca78bbd63ed826018387fd7e6b6841afe7efa1
7
- data.tar.gz: 3e53da8b488c17cfc6f0c168aa59c21d93dc4a1796423efe85cbc36c84b47448ff26cde61f851c24f5faed176fc39202ef383e70d5228975d326fad556728e22
6
+ metadata.gz: 4626a2eeaf870f6a000e048e0d7264aaf8d79486c09eaffd247358f14d83eb2cfe201f758d62b60bfb5a6da4ce5dddbdbc1ce701350920bf88c5f4396a4bb196
7
+ data.tar.gz: e5c098daabeaa492376e42dfbd1639439d09c9f457323e43f4f00c240d8d4a5c91b0d05ce53d41306fb9b28788bdc53115df8a3698f87e41f9d76e8dd548d804
@@ -8,7 +8,7 @@ An ActiveRecord extension to get more from PostgreSQL:
8
8
  * Create/drop [schemas](#schemas).
9
9
  * Use existing functionality in the context of [schemas](#schemas).
10
10
  * Set/remove [comments on columns and tables](#table-and-column-comments).
11
- * [Enhancements to the Rails 4.2 foreign key support](#foreign-keys).
11
+ * [Enhancements to the Rails 4.2 foreign key support (PgSaurus 3.X)](#foreign-keys).
12
12
  * Use [partial indexes](#partial-indexes).
13
13
  * Use [indexes on expressions](#indexes-on-expressions).
14
14
  * Use [indexes with custom ops classes](#indexes-with-operator-classes).
@@ -33,12 +33,10 @@ PgSaurus is a fork of PgPower.
33
33
 
34
34
  ## Environment notes
35
35
 
36
- PgSaurus v3 was tested with Rails 4.2, Ruby 2.2.4. For Rails 4.1, use PgSaurus v2.5+.
36
+ PgSaurus v4 was tested with Rails 5.2 and Ruby 2.4. For Rails 4.2, use PgSaurus v3. For Rails 4.1, use PgSaurus v2.5+.
37
+ Older versions of Rails are not supported.
37
38
 
38
- NOTE: JRuby is not supported. The current ActiveRecord JDBC adapter has its own Rails4-compatible
39
- method named "create_schema" which conflicts with this gem.
40
-
41
- NOTE: PgSaurus does not support Rails 3.
39
+ NOTE: JRuby is not supported.
42
40
 
43
41
  ## Schemas
44
42
 
@@ -179,10 +177,26 @@ If you want to remove the index, pass in the `remove_index: true` option.
179
177
  remove_foreign_key(:comments, column: :post_id, remove_index: true)
180
178
  ```
181
179
 
180
+ ### Migration notes - upgrading from Rails 4.2
181
+
182
+ PgSaurus v4.X requires Rails 5. Rails 5.2 is recommended.
183
+ You can use the new Rails 5 semantics to create comments and indexes inline.
184
+ You also need to use the index order options using the Rails 5 semantics.
185
+
186
+ ```ruby
187
+ # THIS FAILS
188
+ add_index :books, ["author_id DESC NULLS FIRST", "publisher_id DESC NULLS LAST"],
189
+ name: "books_author_id_and_publisher_id"
190
+
191
+ # DO THIS INSTEAD
192
+ add_index :books, ["author_id", "publisher_id"],
193
+ name: "books_author_id_and_publisher_id",
194
+ order: { author_id: "DESC NULLS FIRST", publisher_id: "DESC NULLS LAST" }
195
+ ```
182
196
 
183
197
  ### Migration notes - upgrading from Rails 4.1
184
198
 
185
- PgSaurus v3+ now uses the Rails 4.2 semantics for `add_foreign_key` and `remove_foreign_key`. See http://api.rubyonrails.org/v4.2/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html
199
+ PgSaurus v3.X now uses the Rails 4.2 semantics for `add_foreign_key` and `remove_foreign_key`. See http://api.rubyonrails.org/v4.2/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html
186
200
 
187
201
  A few things have changed. The most breaking change is that the syntax `remove_foreign_key :from_table, :to_table, options` no longer works.
188
202
 
@@ -460,9 +474,9 @@ PgSaurus::Tools.index_exists?(table, columns, options) # => returns true if an
460
474
 
461
475
  ## TODO
462
476
 
463
- Support for Rails 5+
477
+ Support for Rails 6+
464
478
 
465
- * Rails 5 introduces its own schema support. PgSaurus v4+ will have to drop any conflicting support and modify its other features to accommodate Rails 5's schema support.
479
+ * Rails 6 support has not been tested as of yet.
466
480
 
467
481
  Possible support for JRuby:
468
482
 
@@ -490,6 +504,6 @@ Released under the MIT License. See the MIT-LICENSE file for more details.
490
504
 
491
505
  Contributions are welcome. However, before issuing a pull request, please make sure of the following:
492
506
 
493
- * All specs are passing (under ruby 1.9.3+)
507
+ * All specs are passing (under Ruby 2.4+)
494
508
  * Any new features have test coverage.
495
509
  * Anything that breaks backward compatibility has a very good reason for doing so.
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path('..', __dir__)
6
+ ENGINE_PATH = File.expand_path('../lib/pg_saurus/engine', __dir__)
7
+ APP_PATH = File.expand_path('../test/dummy/config/application', __dir__)
8
+
9
+ # Set up gems listed in the Gemfile.
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
11
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
12
+
13
+ require 'rails/all'
14
+ require 'rails/engine/commands'
@@ -9,6 +9,94 @@ module ActiveRecord
9
9
  # Regexp used to find the operator name (or operator string, e.g. "DESC NULLS LAST"):
10
10
  OPERATOR_REGEXP = /(.+?)\s([\w\s]+)$/
11
11
 
12
+ # Regex to find columns used in index statements
13
+ INDEX_COLUMN_EXPRESSION = /ON [\w\.]+(?: USING \w+ )?\((.+)\)/
14
+
15
+ # Regex to find where clause in index statements
16
+ INDEX_WHERE_EXPRESSION = /WHERE (.+)$/
17
+
18
+ # Returns an array of indexes for the given table.
19
+ #
20
+ # == Patch 1:
21
+ # Remove type specification from stored Postgres index definitions.
22
+ #
23
+ # == Patch 2:
24
+ # Split compound functional indexes to array.
25
+ #
26
+ def indexes(table_name)
27
+ scope = quoted_scope(table_name)
28
+
29
+ result = query(<<-SQL, "SCHEMA")
30
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
31
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment
32
+ FROM pg_class t
33
+ INNER JOIN pg_index d ON t.oid = d.indrelid
34
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
35
+ LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
36
+ WHERE i.relkind = 'i'
37
+ AND d.indisprimary = 'f'
38
+ AND t.relname = #{scope[:name]}
39
+ AND n.nspname = #{scope[:schema]}
40
+ ORDER BY i.relname
41
+ SQL
42
+
43
+ result.map do |row|
44
+ index_name = row[0]
45
+ unique = row[1]
46
+ indkey = row[2].split(" ").map(&:to_i)
47
+ inddef = row[3]
48
+ oid = row[4]
49
+ comment = row[5]
50
+
51
+ using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten
52
+
53
+ orders = {}
54
+ opclasses = {}
55
+
56
+ if indkey.include?(0)
57
+ definition = inddef.sub(INDEX_WHERE_EXPRESSION, '')
58
+
59
+ if column_expression = definition.match(INDEX_COLUMN_EXPRESSION)[1]
60
+ columns = split_expression(expressions).map do |functional_name|
61
+ remove_type(functional_name)
62
+ end
63
+
64
+ columns = columns.size > 1 ? columns : columns[0]
65
+ end
66
+ else
67
+ columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
68
+ SELECT a.attnum, a.attname
69
+ FROM pg_attribute a
70
+ WHERE a.attrelid = #{oid}
71
+ AND a.attnum IN (#{indkey.join(",")})
72
+ SQL
73
+
74
+ # add info on sort order (only desc order is explicitly specified, asc is the default)
75
+ # and non-default opclasses
76
+ expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
77
+ opclasses[column] = opclass.to_sym if opclass
78
+ if nulls
79
+ orders[column] = [desc, nulls].compact.join(" ")
80
+ else
81
+ orders[column] = :desc if desc
82
+ end
83
+ end
84
+ end
85
+
86
+ IndexDefinition.new(
87
+ table_name,
88
+ index_name,
89
+ unique,
90
+ columns,
91
+ orders: orders,
92
+ opclasses: opclasses,
93
+ where: where,
94
+ using: using.to_sym,
95
+ comment: comment.presence
96
+ )
97
+ end
98
+ end
99
+
12
100
  # Redefine original add_index method to handle :concurrently option.
13
101
  #
14
102
  # Adds a new index to the table. +column_name+ can be a single Symbol, or
@@ -34,10 +122,11 @@ module ActiveRecord
34
122
 
35
123
  index_name,
36
124
  index_type,
37
- index_columns,
125
+ index_columns_and_opclasses,
38
126
  index_options,
39
127
  index_algorithm,
40
- index_using = add_index_options(table_name, column_name, options)
128
+ index_using,
129
+ comment = add_index_options(table_name, column_name, options)
41
130
 
42
131
  # GOTCHA:
43
132
  # It ensures that there is no existing index only for the case when the index
@@ -64,7 +153,7 @@ module ActiveRecord
64
153
  statements << "ON"
65
154
  statements << quote_table_name(table_name)
66
155
  statements << index_using if index_using.present?
67
- statements << "(#{index_columns})" if index_columns.present? unless skip_column_quoting
156
+ statements << "(#{index_columns_and_opclasses})" if index_columns_and_opclasses.present? unless skip_column_quoting
68
157
  statements << "(#{column_name})" if column_name.present? and skip_column_quoting
69
158
  statements << index_options if index_options.present?
70
159
 
@@ -84,7 +173,7 @@ module ActiveRecord
84
173
  #
85
174
  def index_exists?(table_name, column_name, options = {})
86
175
  column_names = Array.wrap(column_name)
87
- index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
176
+ index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, column: column_names)
88
177
 
89
178
  # Always compare the index name
90
179
  default_comparator = lambda { |index| index.name == index_name }
@@ -133,25 +222,30 @@ module ActiveRecord
133
222
  raise ArgumentError, "You must specify the index name"
134
223
  end
135
224
  else
136
- index_name(table_name, :column => options)
225
+ index_name(table_name, column: options)
137
226
  end
138
227
  end
139
228
 
140
229
  # Override super method to provide support for expression column names.
141
- def quoted_columns_for_index(column_names, options = {})
142
- column_names.map do |name|
143
- column_name, operator_name = split_column_name(name)
230
+ def quoted_columns_for_index(column_names, **options)
231
+ return [column_names] if column_names.is_a?(String)
144
232
 
145
- result_name = if column_name =~ FUNCTIONAL_INDEX_REGEXP
146
- "#{$1}(#{$2}#{quote_column_name($3)})"
147
- else
148
- quote_column_name(column_name)
149
- end
233
+ quoted_columns = Hash[
234
+ column_names.map do |name|
235
+ column_name, operator_name = split_column_name(name)
150
236
 
151
- result_name += " " + operator_name if operator_name
237
+ result_name = if column_name =~ FUNCTIONAL_INDEX_REGEXP
238
+ _name = "#{$1}(#{$2}#{quote_column_name($3)})"
239
+ _name += " #{operator_name}"
240
+ _name
241
+ else
242
+ quote_column_name(column_name).dup
243
+ end
244
+ [column_name.to_sym, result_name]
245
+ end
246
+ ]
152
247
 
153
- result_name
154
- end
248
+ add_options_for_index_columns(quoted_columns, options).values
155
249
  end
156
250
  protected :quoted_columns_for_index
157
251
 
@@ -180,6 +274,47 @@ module ActiveRecord
180
274
  end
181
275
  end
182
276
  private :split_column_name
277
+
278
+ # Splits only on commas outside of parens
279
+ def split_expression(expression)
280
+ result = []
281
+ parens = 0
282
+ buffer = ""
283
+
284
+ expression.chars do |char|
285
+ case char
286
+ when ','
287
+ if parens == 0
288
+ result.push(buffer)
289
+ buffer = ""
290
+ next
291
+ end
292
+ when '('
293
+ parens += 1
294
+ when ')'
295
+ parens -= 1
296
+ end
297
+
298
+ buffer << char
299
+ end
300
+
301
+ result << buffer unless buffer.empty?
302
+ result
303
+ end
304
+ private :split_expression
305
+
306
+ # Remove type specification from stored Postgres index definitions
307
+ #
308
+ # @param [String] column_with_type the name of the column with type
309
+ # @return [String]
310
+ #
311
+ # @example
312
+ # remove_type("((col)::text")
313
+ # => "col"
314
+ def remove_type(column_with_type)
315
+ column_with_type.sub(/\((\w+)\)::\w+/, '\1')
316
+ end
317
+ private :remove_type
183
318
  end
184
319
  end
185
320
  end
@@ -5,48 +5,26 @@ module ActiveRecord #:nodoc:
5
5
  class SchemaDumper #:nodoc:
6
6
  # Writes out index-related details to the schema stream
7
7
  #
8
- # == Patch reason:
9
- # {ActiveRecord::SchemaDumper#indexes} does not support writing out
10
- # details related to partial indexes.
11
- #
12
8
  # == Patch:
13
- # Append :where clause if there's a partial index
9
+ # Add support of skip_column_quoting option for json indexes.
14
10
  #
15
- def indexes(table, stream)
16
- if (indexes = @connection.indexes(table)).any?
17
- add_index_statements = indexes.map do |index|
18
- columns = index.columns.map do |column_name|
19
- column_name += ' ' +index.operators[column_name] if index.operators.key?(column_name)
20
-
21
- column_name
22
- end
23
-
24
- is_json_index = (columns.count == 1 && columns.first =~ /^(.+->.+)$/)
25
-
26
- statement_parts = [
27
- ('add_index ' + index.table.inspect),
28
- is_json_index ? "\"#{columns.first}\"" : columns.inspect,
29
- (':name => ' + index.name.inspect),
30
- ]
31
- statement_parts << ':unique => true' if index.unique
32
-
33
- index_lengths = (index.lengths || []).compact
34
- statement_parts << (':length => ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty?
35
-
36
- # Patch
37
- # Append :where clause if a partial index
38
- statement_parts << (':where => ' + index.where.inspect) if index.where
39
-
40
- statement_parts << (':using => ' + index.access_method.inspect) unless index.access_method.downcase == 'btree'
41
-
42
- statement_parts << ':skip_column_quoting => true' if is_json_index
43
-
44
- ' ' + statement_parts.join(', ')
45
- end
46
-
47
- stream.puts add_index_statements.sort.join("\n")
48
- stream.puts
49
- end
11
+ def index_parts(index)
12
+ is_json_index = index.columns.is_a?(String) && index.columns =~ /^(.+->.+)$/
13
+
14
+ index_parts = [
15
+ index.columns.inspect,
16
+ "name: #{index.name.inspect}",
17
+ ]
18
+ index_parts << "unique: true" if index.unique
19
+ index_parts << "length: #{format_index_parts(index.lengths)}" if index.lengths.present?
20
+ index_parts << "order: #{format_index_parts(index.orders)}" if index.orders.present?
21
+ index_parts << "opclass: #{format_index_parts(index.opclasses)}" if index.opclasses.present?
22
+ index_parts << "where: #{index.where.inspect}" if index.where
23
+ index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index)
24
+ index_parts << "skip_column_quoting: true" if is_json_index
25
+ index_parts << "type: #{index.type.inspect}" if index.type
26
+ index_parts << "comment: #{index.comment.inspect}" if index.comment
27
+ index_parts
50
28
  end
51
29
  end
52
30
  end
@@ -4,7 +4,6 @@ module PgSaurus::ConnectionAdapters # :nodoc:
4
4
  autoload :AbstractAdapter
5
5
  autoload :PostgreSQLAdapter, 'pg_saurus/connection_adapters/postgresql_adapter'
6
6
  autoload :Table
7
- autoload :IndexDefinition, 'pg_saurus/connection_adapters/index_definition'
8
7
  autoload :FunctionDefinition, 'pg_saurus/connection_adapters/function_definition'
9
8
  autoload :TriggerDefinition, 'pg_saurus/connection_adapters/trigger_definition'
10
9
  end
@@ -3,16 +3,16 @@
3
3
  module PgSaurus::ConnectionAdapters::PostgreSQLAdapter::ExtensionMethods
4
4
  # Default options for {#create_extension} method.
5
5
  CREATE_EXTENSION_DEFAULTS = {
6
- :if_not_exists => true,
7
- :schema_name => nil,
8
- :version => nil,
9
- :old_version => nil
6
+ if_not_exists: true,
7
+ schema_name: nil,
8
+ version: nil,
9
+ old_version: nil
10
10
  }
11
11
 
12
12
  # Default options for {#drop_extension} method.
13
13
  DROP_EXTENSION_DEFAULTS = {
14
- :if_exists => true,
15
- :mode => :restrict
14
+ if_exists: true,
15
+ mode: :restrict
16
16
  }
17
17
 
18
18
  # The modes which determine postgresql behavior on DROP EXTENSION operation.
@@ -20,8 +20,8 @@ module PgSaurus::ConnectionAdapters::PostgreSQLAdapter::ExtensionMethods
20
20
  # *:restrict* refuse to drop the extension if any objects depend on it
21
21
  # *:cascade* automatically drop objects that depend on the extension
22
22
  AVAILABLE_DROP_MODES = {
23
- :restrict => 'RESTRICT',
24
- :cascade => 'CASCADE'
23
+ restrict: 'RESTRICT',
24
+ cascade: 'CASCADE'
25
25
  }
26
26
 
27
27
  # @return [Boolean] if adapter supports postgresql extension manipulation
@@ -127,8 +127,8 @@ module PgSaurus::ConnectionAdapters::PostgreSQLAdapter::ExtensionMethods
127
127
  [
128
128
  row['ext_name'],
129
129
  {
130
- :schema_name => row['schema_name'],
131
- :version => row['ext_version']
130
+ schema_name: row['schema_name'],
131
+ version: row['ext_version']
132
132
  }
133
133
  ]
134
134
  end
@@ -2,7 +2,6 @@ module PgSaurus # :nodoc:
2
2
  # Provides methods to extend {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter}
3
3
  # to support foreign keys feature.
4
4
  module ConnectionAdapters::PostgreSQLAdapter::ForeignKeyMethods
5
-
6
5
  # Drop table and optionally disable triggers.
7
6
  # Changes adapted from https://github.com/matthuhiggins/foreigner/blob/e72ab9c454c156056d3f037d55e3359cd972af32/lib/foreigner/connection_adapters/sql2003.rb
8
7
  # NOTE: Disabling referential integrity requires superuser access in postgres.
@@ -22,7 +21,8 @@ module PgSaurus # :nodoc:
22
21
  end
23
22
 
24
23
  # See activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
25
- # Creates index on the FK column by default. Pass in the option :exclude_index => true
24
+ #
25
+ # Creates index on the FK column by default. Pass in the option exclude_index: true
26
26
  # to disable this.
27
27
  def add_foreign_key(from_table, to_table, options = {})
28
28
  exclude_index = (options.has_key?(:exclude_index) ? options.delete(:exclude_index) : false)
@@ -42,23 +42,30 @@ module PgSaurus # :nodoc:
42
42
  end
43
43
 
44
44
  # See activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
45
+ #
46
+ # Pass in the option remove_index: true to remove index as well.
45
47
  def remove_foreign_key(from_table, options_or_to_table = {})
46
48
  if options_or_to_table.is_a?(Hash) && options_or_to_table[:remove_index]
47
49
  column = options_or_to_table[:column]
48
50
  remove_index from_table, column
51
+ options_or_to_table.delete(:remove_index)
49
52
  end
50
53
 
51
54
  super(from_table, options_or_to_table)
52
55
  end
53
56
 
54
57
  # See: activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
58
+ #
59
+ # Removes schema name from table name.
55
60
  def foreign_key_column_for(table_name)
56
61
  table = table_name.to_s.split('.').last
57
62
 
58
63
  super table
59
64
  end
60
65
 
61
- # see activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
66
+ # See activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
67
+ #
68
+ # Add from_schema option to foreign key definition options.
62
69
  def foreign_keys(table_name)
63
70
  namespace = table_name.to_s.split('.').first
64
71
  table_name = table_name.to_s.split('.').last
@@ -99,7 +106,5 @@ module PgSaurus # :nodoc:
99
106
  ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, row['to_table'], options)
100
107
  end
101
108
  end
102
-
103
-
104
109
  end
105
110
  end
@@ -18,25 +18,4 @@ module PgSaurus::ConnectionAdapters::PostgreSQLAdapter::IndexMethods
18
18
  def index_name(table_name, options) #:nodoc:
19
19
  super.gsub('.','_')
20
20
  end
21
-
22
- # Overrides ActiveRecord::ConnectionAdapters::SchemaStatements.index_name_for_remove
23
- # to support schema notation. Prepends the schema name to the index name.
24
- #
25
- # === Example
26
- # drop_index 'demography.citizens', :country_id
27
- # # produces
28
- # DROP INDEX "demography"."index_demography_citizens_on_country_id"
29
- # # instead of
30
- # DROP INDEX "index_demography_citizens_on_country_id"
31
- #
32
- def index_name_for_remove(table_name, options = {})
33
- index_name = super
34
-
35
- if table_name.include?('.') # schema notation
36
- schema = table_name.split('.').first
37
- "#{schema}.#{index_name}"
38
- else
39
- index_name
40
- end
41
- end
42
21
  end
@@ -1,18 +1,6 @@
1
1
  # Provides methods to extend {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter}
2
2
  # to support schemas feature.
3
3
  module PgSaurus::ConnectionAdapters::PostgreSQLAdapter::SchemaMethods
4
- # Creates new schema in DB.
5
- # @param [String] schema_name
6
- def create_schema(schema_name)
7
- ::PgSaurus::Tools.create_schema(schema_name)
8
- end
9
-
10
- # Drops schema in DB.
11
- # @param [String] schema_name
12
- def drop_schema(schema_name)
13
- ::PgSaurus::Tools.drop_schema(schema_name)
14
- end
15
-
16
4
  # Move table to another schema
17
5
  # @param [String] table table name. Can be with schema prefix e.g. "demography.people"
18
6
  # @param [String] schema schema where table should be moved to.
@@ -12,7 +12,7 @@ module PgSaurus::ConnectionAdapters::PostgreSQLAdapter::TranslateException
12
12
  when INSUFFICIENT_PRIVILEGE
13
13
  exc_message = exception_result.try(:error_field, PG::Result::PG_DIAG_MESSAGE_PRIMARY)
14
14
  exc_message ||= message
15
- ::ActiveRecord::InsufficientPrivilege.new(exc_message, exception)
15
+ ::ActiveRecord::InsufficientPrivilege.new(exc_message)
16
16
  else
17
17
  super
18
18
  end
@@ -15,11 +15,7 @@ module PgSaurus::ConnectionAdapters::PostgreSQLAdapter::TriggerMethods
15
15
  for_each = options[:for_each] || 'ROW'
16
16
  constraint = options[:constraint]
17
17
 
18
- sql = if constraint
19
- "CREATE CONSTRAINT TRIGGER #{trigger_name(proc_name, options)}\n #{event}\n"
20
- else
21
- "CREATE TRIGGER #{trigger_name(proc_name, options)}\n #{event}\n"
22
- end
18
+ sql = "CREATE #{!!constraint ? "CONSTRAINT " : ""}TRIGGER #{trigger_name(proc_name, options)}\n #{event}\n"
23
19
 
24
20
  sql << " ON #{quote_table_or_view(table_name, options)}\n"
25
21
  if constraint
@@ -133,7 +133,7 @@ module PgSaurus::CreateIndexConcurrently
133
133
  module MigrationProxy
134
134
  # :nodoc:
135
135
  def self.included(klass)
136
- klass.delegate :process_postponed_queries, :to => :migration
136
+ klass.delegate :process_postponed_queries, to: :migration
137
137
  end
138
138
  end
139
139
 
@@ -2,14 +2,11 @@ module PgSaurus
2
2
  # :nodoc:
3
3
  class Engine < Rails::Engine
4
4
 
5
- initializer 'pg_saurus' do
5
+ initializer "pg_saurus" do
6
6
  ActiveSupport.on_load(:active_record) do
7
7
  # load monkey patches
8
- ['schema_dumper',
9
- 'errors',
10
- 'connection_adapters/postgresql_adapter',
11
- 'connection_adapters/abstract/schema_statements'].each do |path|
12
- require ::PgSaurus::Engine.root + 'lib/core_ext/active_record/' + path
8
+ %w(schema_dumper errors connection_adapters/postgresql/schema_statements).each do |path|
9
+ require ::PgSaurus::Engine.root + "lib/core_ext/active_record/" + path
13
10
  end
14
11
 
15
12
  ActiveRecord::SchemaDumper.class_eval do
@@ -35,14 +32,16 @@ module PgSaurus
35
32
  end
36
33
  end
37
34
 
38
- # Follow three include statements add support for concurrently
39
- # index creation in migrations.
35
+ # The following three include statements add support for concurrently
36
+ # creating indexes in migrations.
40
37
  ActiveRecord::Migration.class_eval do
41
38
  include ::PgSaurus::CreateIndexConcurrently::Migration
42
39
  end
40
+
43
41
  ActiveRecord::Migrator.class_eval do
44
42
  prepend PgSaurus::CreateIndexConcurrently::Migrator
45
43
  end
44
+
46
45
  ActiveRecord::MigrationProxy.class_eval do
47
46
  include ::PgSaurus::CreateIndexConcurrently::MigrationProxy
48
47
  end
@@ -56,13 +55,7 @@ module PgSaurus
56
55
  include ::PgSaurus::ConnectionAdapters::AbstractAdapter
57
56
  end
58
57
 
59
- if defined?(ActiveRecord::ConnectionAdapters::JdbcAdapter)
60
- sql_adapter_class = ActiveRecord::ConnectionAdapters::JdbcAdapter
61
- else
62
- sql_adapter_class = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
63
- end
64
-
65
- sql_adapter_class.class_eval do
58
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
66
59
  prepend ::PgSaurus::ConnectionAdapters::PostgreSQLAdapter::SchemaMethods
67
60
  prepend ::PgSaurus::ConnectionAdapters::PostgreSQLAdapter::ForeignKeyMethods
68
61
 
@@ -1,29 +1,17 @@
1
1
  # Provides methods to extend ActiveRecord::Migration::CommandRecorder to
2
2
  # support multi schemas feature.
3
3
  module PgSaurus::Migration::CommandRecorder::SchemaMethods
4
- # :nodoc:
5
- def create_schema(*args)
6
- record(:create_schema, args)
7
- end
8
-
9
- # :nodoc:
10
- def drop_schema(*args)
11
- record(:drop_schema, args)
12
- end
13
-
14
- # :nodoc:
15
- def move_table_to_schema(*args)
16
- record(:move_table_to_schema, args)
17
- end
18
-
19
- # :nodoc:
20
- def create_schema_if_not_exists(*args)
21
- record(:create_schema_if_not_exists, args)
22
- end
23
4
 
24
- # :nodoc:
25
- def drop_schema_if_exists(*args)
26
- record(:drop_schema_if_exists, args)
5
+ [
6
+ :create_schema,
7
+ :drop_schema,
8
+ :move_table_to_schema,
9
+ :create_schema_if_not_exists,
10
+ :drop_schema_if_exists
11
+ ].each do |method_name|
12
+ define_method(method_name) do |*args|
13
+ record method_name, args
14
+ end
27
15
  end
28
16
 
29
17
  # :nodoc:
@@ -37,7 +37,7 @@ module PgSaurus::SchemaDumper::ForeignKeyMethods
37
37
  # If an index was created in a migration, it will get dumped to the schema
38
38
  # separately from the foreign key. This will raise an exception if
39
39
  # add_foreign_key is run without :exclude_index => true.
40
- parts << ":exclude_index => true"
40
+ parts << "exclude_index: true"
41
41
 
42
42
  " #{parts.join(', ')}"
43
43
  end
@@ -12,38 +12,22 @@ module PgSaurus
12
12
  module Tools
13
13
  extend self
14
14
 
15
- # Creates PostgreSQL schema
16
- def create_schema(schema_name)
17
- sql = %{CREATE SCHEMA "#{schema_name}"}
18
- connection.execute sql
19
- end
20
-
21
15
  # Create a schema if it does not exist yet.
22
16
  #
23
17
  # @note
24
- # CREATE SCHEMA IF EXISTS appeared only in PostgreSQL 9.3
25
- # It is not used here for backward compatibility.
18
+ # Supports PostgreSQL 9.3+
26
19
  #
27
20
  # @return [void]
28
21
  def create_schema_if_not_exists(schema_name)
29
- unless schemas.include?(schema_name.to_s)
30
- create_schema(schema_name)
31
- end
22
+ sql = %{CREATE SCHEMA IF NOT EXISTS "#{schema_name}"}
23
+ connection.execute sql
32
24
  end
33
25
 
34
26
  # Ensure schema does not exists.
35
27
  #
36
28
  # @return [void]
37
29
  def drop_schema_if_exists(schema_name)
38
- if schemas.include?(schema_name.to_s)
39
- drop_schema(schema_name)
40
- end
41
- end
42
-
43
- # Drops PostgreSQL schema
44
- def drop_schema(schema_name)
45
- sql = %{DROP SCHEMA "#{schema_name}"}
46
- connection.execute sql
30
+ connection.drop_schema(schema_name, if_exists: true)
47
31
  end
48
32
 
49
33
  # Returns an array of existing schemas.
@@ -1,4 +1,4 @@
1
1
  module PgSaurus
2
2
  # Version of pg_saurus gem.
3
- VERSION = "3.7.1"
3
+ VERSION = "4.0.0"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_saurus
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.7.1
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Potapov Sergey
@@ -13,78 +13,78 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2019-10-18 00:00:00.000000000 Z
16
+ date: 2019-10-24 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: pg
20
20
  requirement: !ruby/object:Gem::Requirement
21
21
  requirements:
22
- - - "<"
22
+ - - ">="
23
23
  - !ruby/object:Gem::Version
24
- version: '1.0'
24
+ version: '0'
25
25
  type: :runtime
26
26
  prerelease: false
27
27
  version_requirements: !ruby/object:Gem::Requirement
28
28
  requirements:
29
- - - "<"
29
+ - - ">="
30
30
  - !ruby/object:Gem::Version
31
- version: '1.0'
31
+ version: '0'
32
32
  - !ruby/object:Gem::Dependency
33
33
  name: railties
34
34
  requirement: !ruby/object:Gem::Requirement
35
35
  requirements:
36
36
  - - "~>"
37
37
  - !ruby/object:Gem::Version
38
- version: '4.2'
38
+ version: 5.2.3
39
39
  type: :runtime
40
40
  prerelease: false
41
41
  version_requirements: !ruby/object:Gem::Requirement
42
42
  requirements:
43
43
  - - "~>"
44
44
  - !ruby/object:Gem::Version
45
- version: '4.2'
45
+ version: 5.2.3
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: activemodel
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  requirements:
50
50
  - - "~>"
51
51
  - !ruby/object:Gem::Version
52
- version: '4.2'
52
+ version: 5.2.3
53
53
  type: :runtime
54
54
  prerelease: false
55
55
  version_requirements: !ruby/object:Gem::Requirement
56
56
  requirements:
57
57
  - - "~>"
58
58
  - !ruby/object:Gem::Version
59
- version: '4.2'
59
+ version: 5.2.3
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: activerecord
62
62
  requirement: !ruby/object:Gem::Requirement
63
63
  requirements:
64
64
  - - "~>"
65
65
  - !ruby/object:Gem::Version
66
- version: '4.2'
66
+ version: 5.2.3
67
67
  type: :runtime
68
68
  prerelease: false
69
69
  version_requirements: !ruby/object:Gem::Requirement
70
70
  requirements:
71
71
  - - "~>"
72
72
  - !ruby/object:Gem::Version
73
- version: '4.2'
73
+ version: 5.2.3
74
74
  - !ruby/object:Gem::Dependency
75
75
  name: activesupport
76
76
  requirement: !ruby/object:Gem::Requirement
77
77
  requirements:
78
78
  - - "~>"
79
79
  - !ruby/object:Gem::Version
80
- version: '4.2'
80
+ version: 5.2.3
81
81
  type: :runtime
82
82
  prerelease: false
83
83
  version_requirements: !ruby/object:Gem::Requirement
84
84
  requirements:
85
85
  - - "~>"
86
86
  - !ruby/object:Gem::Version
87
- version: '4.2'
87
+ version: 5.2.3
88
88
  - !ruby/object:Gem::Dependency
89
89
  name: rspec-rails
90
90
  requirement: !ruby/object:Gem::Requirement
@@ -183,6 +183,20 @@ dependencies:
183
183
  - - ">="
184
184
  - !ruby/object:Gem::Version
185
185
  version: '0'
186
+ - !ruby/object:Gem::Dependency
187
+ name: rubocop
188
+ requirement: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
193
+ type: :development
194
+ prerelease: false
195
+ version_requirements: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
186
200
  description: ActiveRecord extensions for PostgreSQL. Provides useful tools for schema,
187
201
  foreign_key, index, function, trigger, comment and extension manipulations in migrations.
188
202
  email:
@@ -191,15 +205,16 @@ email:
191
205
  - cryo28@gmail.com
192
206
  - matt.dressel@gmail.com
193
207
  - rubygems.org@bruceburdick.com
194
- executables: []
208
+ executables:
209
+ - rails
195
210
  extensions: []
196
211
  extra_rdoc_files:
197
212
  - README.markdown
198
213
  files:
199
214
  - README.markdown
215
+ - bin/rails
200
216
  - lib/colorized_text.rb
201
- - lib/core_ext/active_record/connection_adapters/abstract/schema_statements.rb
202
- - lib/core_ext/active_record/connection_adapters/postgresql_adapter.rb
217
+ - lib/core_ext/active_record/connection_adapters/postgresql/schema_statements.rb
203
218
  - lib/core_ext/active_record/errors.rb
204
219
  - lib/core_ext/active_record/schema_dumper.rb
205
220
  - lib/generators/pg_saurus/install/install_generator.rb
@@ -215,7 +230,6 @@ files:
215
230
  - lib/pg_saurus/connection_adapters/abstract_adapter/trigger_methods.rb
216
231
  - lib/pg_saurus/connection_adapters/foreign_key_definition.rb
217
232
  - lib/pg_saurus/connection_adapters/function_definition.rb
218
- - lib/pg_saurus/connection_adapters/index_definition.rb
219
233
  - lib/pg_saurus/connection_adapters/postgresql_adapter.rb
220
234
  - lib/pg_saurus/connection_adapters/postgresql_adapter/comment_methods.rb
221
235
  - lib/pg_saurus/connection_adapters/postgresql_adapter/extension_methods.rb
@@ -1,261 +0,0 @@
1
- module ActiveRecord # :nodoc:
2
- module ConnectionAdapters # :nodoc:
3
- # Patched version: 3.1.3
4
- # Patched methods::
5
- # * indexes
6
- class PostgreSQLAdapter
7
- # Regex to find columns used in index statements
8
- INDEX_COLUMN_EXPRESSION = /ON [\w\.]+(?: USING \w+ )?\((.+)\)/
9
- # Regex to find where clause in index statements
10
- INDEX_WHERE_EXPRESSION = /WHERE (.+)$/
11
-
12
- # Taken from https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_index.h#L75
13
- # Values are in reverse order
14
- INDOPTION_DESC = 1
15
- # NULLs are first instead of last
16
- INDOPTION_NULLS_FIRST = 2
17
-
18
- # Returns the list of all tables in the schema search path or a specified schema.
19
- #
20
- # == Patch:
21
- # If current user is not `postgres` original method return all tables from all schemas
22
- # without schema prefix. This disables such behavior by querying only default schema.
23
- # Tables with schemas will be queried later.
24
- #
25
- def tables(name = nil)
26
- query(<<-SQL, 'SCHEMA').map { |row| row[0] }
27
- SELECT tablename
28
- FROM pg_tables
29
- WHERE schemaname = ANY (ARRAY['public'])
30
- SQL
31
- end
32
-
33
- # Checks if index exists for given table.
34
- #
35
- # == Patch:
36
- # Search using provided schema if table_name includes schema name.
37
- #
38
- def index_name_exists?(table_name, index_name, default)
39
- postgre_sql_name = PostgreSQL::Utils.extract_schema_qualified_name(table_name)
40
- schema, table = postgre_sql_name.schema, postgre_sql_name.identifier
41
- schemas = schema ? "ARRAY['#{schema}']" : 'current_schemas(false)'
42
-
43
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
44
- SELECT COUNT(*)
45
- FROM pg_class t
46
- INNER JOIN pg_index d ON t.oid = d.indrelid
47
- INNER JOIN pg_class i ON d.indexrelid = i.oid
48
- WHERE i.relkind = 'i'
49
- AND i.relname = '#{index_name}'
50
- AND t.relname = '#{table}'
51
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (#{schemas}) )
52
- SQL
53
- end
54
-
55
- # Returns an array of indexes for the given table.
56
- #
57
- # == Patch 1 reason:
58
- # Since {ActiveRecord::SchemaDumper#tables} is patched to process tables
59
- # with a schema prefix, the {#indexes} method receives table_name as
60
- # "<schema>.<table>". This patch allows it to handle table names with
61
- # a schema prefix.
62
- #
63
- # == Patch 1:
64
- # Search using provided schema if table_name includes schema name.
65
- #
66
- # == Patch 2 reason:
67
- # {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#indexes} is patched
68
- # to support partial indexes using :where clause.
69
- #
70
- # == Patch 2:
71
- # Search the postgres indexdef for the where clause and pass the output to
72
- # the custom {PgSaurus::ConnectionAdapters::IndexDefinition}
73
- #
74
- def indexes(table_name, name = nil)
75
- postgre_sql_name = PostgreSQL::Utils.extract_schema_qualified_name(table_name)
76
- schema, table = postgre_sql_name.schema, postgre_sql_name.identifier
77
- schemas = schema ? "ARRAY['#{schema}']" : 'current_schemas(false)'
78
-
79
- result = query(<<-SQL, name)
80
- SELECT distinct i.relname,
81
- d.indisunique,
82
- d.indkey,
83
- pg_get_indexdef(d.indexrelid),
84
- t.oid,
85
- am.amname,
86
- d.indclass,
87
- d.indoption
88
- FROM pg_class t
89
- INNER JOIN pg_index d ON t.oid = d.indrelid
90
- INNER JOIN pg_class i ON d.indexrelid = i.oid
91
- INNER JOIN pg_am am ON i.relam = am.oid
92
- WHERE i.relkind = 'i'
93
- AND d.indisprimary = 'f'
94
- AND t.relname = '#{table}'
95
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (#{schemas}) )
96
- ORDER BY i.relname
97
- SQL
98
-
99
- result.map do |row|
100
- index = {
101
- :name => row[0],
102
- :unique => row[1] == 't',
103
- :keys => row[2].split(" "),
104
- :definition => row[3],
105
- :id => row[4],
106
- :access_method => row[5],
107
- :operators => row[6].split(" "),
108
- :options => row[7].split(" ").map(&:to_i)
109
- }
110
-
111
- column_names = find_column_names(table_name, index)
112
-
113
- operator_names = find_operator_names(column_names, index)
114
-
115
- unless column_names.empty?
116
- where = find_where_statement(index)
117
- lengths = find_lengths(index)
118
-
119
- PgSaurus::ConnectionAdapters::IndexDefinition.new(
120
- table_name,
121
- index[:name],
122
- index[:unique],
123
- column_names,
124
- lengths,
125
- where,
126
- index[:access_method],
127
- operator_names
128
- )
129
- end
130
- end.compact
131
- end
132
-
133
- # Find column names from index attributes. If the columns are virtual (i.e.
134
- # this is an expression index) then it will try to return the functions
135
- # that represent each column.
136
- #
137
- # @param [String] table_name the name of the table, possibly schema-qualified
138
- # @param [Hash] index index attributes
139
- # @return [Array]
140
- def find_column_names(table_name, index)
141
- columns = Hash[query(<<-SQL, "Columns for index #{index[:name]} on #{table_name}")]
142
- SELECT a.attnum, a.attname
143
- FROM pg_attribute a
144
- WHERE a.attrelid = #{index[:id]}
145
- AND a.attnum IN (#{index[:keys].join(",")})
146
- SQL
147
-
148
- column_names = columns.values_at(*index[:keys]).compact
149
-
150
- if column_names.empty?
151
- definition = index[:definition].sub(INDEX_WHERE_EXPRESSION, '')
152
- if column_expression = definition.match(INDEX_COLUMN_EXPRESSION)[1]
153
- column_names = split_expression(column_expression).map do |functional_name|
154
- remove_type(functional_name)
155
- end
156
- end
157
- else
158
- # In case if column_names if not empty it contains list of column name taken from pg_attribute table.
159
- # So we need to check indoption column and add DESC and NULLS LAST based on its value.
160
- # https://stackoverflow.com/questions/18121103/how-to-get-the-index-column-orderasc-desc-nulls-first-from-postgresql/18128457#18128457
161
- column_names = column_names.map.with_index do |column_name, column_index|
162
- option = index[:options][column_index]
163
-
164
- if option != 0
165
- column_name << " DESC" if option & INDOPTION_DESC > 0
166
-
167
- if option & INDOPTION_NULLS_FIRST > 0
168
- column_name << " NULLS FIRST"
169
- else
170
- column_name << " NULLS LAST"
171
- end
172
- end
173
-
174
- column_name
175
- end
176
- end
177
-
178
- column_names
179
- end
180
-
181
- # Find non-default operator class names for columns from index.
182
- #
183
- # @param column_names [Array] List of columns from index.
184
- # @param index [Hash] index index attributes
185
- # @return [Hash]
186
- def find_operator_names(column_names, index)
187
- column_names.each_with_index.inject({}) do |class_names, (column_name, column_index)|
188
- result = query(<<-SQL, "Classes for columns for index #{index[:name]} for column #{column_name}")
189
- SELECT op.opcname, op.opcdefault
190
- FROM pg_opclass op
191
- WHERE op.oid = #{index[:operators][column_index]};
192
- SQL
193
-
194
- row = result.first
195
-
196
- if row && row[1] == "f"
197
- class_names[column_name] = row[0]
198
- end
199
-
200
- class_names
201
- end
202
- end
203
-
204
- # Splits only on commas outside of parens
205
- def split_expression(expression)
206
- result = []
207
- parens = 0
208
- buffer = ""
209
-
210
- expression.chars do |char|
211
- case char
212
- when ','
213
- if parens == 0
214
- result.push(buffer)
215
- buffer = ""
216
- next
217
- end
218
- when '('
219
- parens += 1
220
- when ')'
221
- parens -= 1
222
- end
223
-
224
- buffer << char
225
- end
226
-
227
- result << buffer unless buffer.empty?
228
- result
229
- end
230
-
231
- # Find where statement from index definition
232
- #
233
- # @param [Hash] index index attributes
234
- # @return [String] where statement
235
- def find_where_statement(index)
236
- index[:definition].scan(INDEX_WHERE_EXPRESSION).flatten[0]
237
- end
238
-
239
- # Find length of index
240
- # TODO Update lengths once we merge in ActiveRecord code that supports it. -dresselm 20120305
241
- #
242
- # @param [Hash] index index attributes
243
- # @return [Array]
244
- def find_lengths(index)
245
- []
246
- end
247
-
248
- # Remove type specification from stored Postgres index definitions
249
- #
250
- # @param [String] column_with_type the name of the column with type
251
- # @return [String]
252
- #
253
- # @example
254
- # remove_type("((col)::text")
255
- # => "col"
256
- def remove_type(column_with_type)
257
- column_with_type.sub(/\((\w+)\)::\w+/, '\1')
258
- end
259
- end
260
- end
261
- end
@@ -1,8 +0,0 @@
1
- module PgSaurus::ConnectionAdapters
2
- # Structure to store index parameters
3
- # Overrides ActiveRecord::ConnectionAdapters::IndexDefinition
4
- # with the additional :where parameter.
5
- class IndexDefinition < Struct.new( :table, :name, :unique, :columns,
6
- :lengths, :where, :access_method, :operators )
7
- end
8
- end