pg_saurus 3.7.1 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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