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 +4 -4
- data/README.markdown +24 -10
- data/bin/rails +14 -0
- data/lib/core_ext/active_record/connection_adapters/{abstract → postgresql}/schema_statements.rb +151 -16
- data/lib/core_ext/active_record/schema_dumper.rb +18 -40
- data/lib/pg_saurus/connection_adapters.rb +0 -1
- data/lib/pg_saurus/connection_adapters/postgresql_adapter/extension_methods.rb +10 -10
- data/lib/pg_saurus/connection_adapters/postgresql_adapter/foreign_key_methods.rb +10 -5
- data/lib/pg_saurus/connection_adapters/postgresql_adapter/index_methods.rb +0 -21
- data/lib/pg_saurus/connection_adapters/postgresql_adapter/schema_methods.rb +0 -12
- data/lib/pg_saurus/connection_adapters/postgresql_adapter/translate_exception.rb +1 -1
- data/lib/pg_saurus/connection_adapters/postgresql_adapter/trigger_methods.rb +1 -5
- data/lib/pg_saurus/create_index_concurrently.rb +1 -1
- data/lib/pg_saurus/engine.rb +8 -15
- data/lib/pg_saurus/migration/command_recorder/schema_methods.rb +10 -22
- data/lib/pg_saurus/schema_dumper/foreign_key_methods.rb +1 -1
- data/lib/pg_saurus/tools.rb +4 -20
- data/lib/pg_saurus/version.rb +1 -1
- metadata +32 -18
- data/lib/core_ext/active_record/connection_adapters/postgresql_adapter.rb +0 -261
- data/lib/pg_saurus/connection_adapters/index_definition.rb +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b32de47fc9d97bfc956d5e2e26c9601ed2da3866dea9a51b97722c90d4b1db7
|
4
|
+
data.tar.gz: 497db5f622d6332efa57216e13527b71a2895125856330e320166f342e088637
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4626a2eeaf870f6a000e048e0d7264aaf8d79486c09eaffd247358f14d83eb2cfe201f758d62b60bfb5a6da4ce5dddbdbc1ce701350920bf88c5f4396a4bb196
|
7
|
+
data.tar.gz: e5c098daabeaa492376e42dfbd1639439d09c9f457323e43f4f00c240d8d4a5c91b0d05ce53d41306fb9b28788bdc53115df8a3698f87e41f9d76e8dd548d804
|
data/README.markdown
CHANGED
@@ -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
|
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.
|
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
|
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
|
477
|
+
Support for Rails 6+
|
464
478
|
|
465
|
-
* Rails
|
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
|
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.
|
data/bin/rails
ADDED
@@ -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'
|
data/lib/core_ext/active_record/connection_adapters/{abstract → postgresql}/schema_statements.rb
RENAMED
@@ -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
|
-
|
125
|
+
index_columns_and_opclasses,
|
38
126
|
index_options,
|
39
127
|
index_algorithm,
|
40
|
-
index_using
|
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 << "(#{
|
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, :
|
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, :
|
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
|
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
|
-
|
146
|
-
|
147
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
9
|
+
# Add support of skip_column_quoting option for json indexes.
|
14
10
|
#
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
:
|
7
|
-
:
|
8
|
-
:
|
9
|
-
:
|
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
|
-
:
|
15
|
-
:
|
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
|
-
:
|
24
|
-
:
|
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
|
-
:
|
131
|
-
:
|
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
|
-
#
|
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
|
-
#
|
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
|
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 =
|
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, :
|
136
|
+
klass.delegate :process_postponed_queries, to: :migration
|
137
137
|
end
|
138
138
|
end
|
139
139
|
|
data/lib/pg_saurus/engine.rb
CHANGED
@@ -2,14 +2,11 @@ module PgSaurus
|
|
2
2
|
# :nodoc:
|
3
3
|
class Engine < Rails::Engine
|
4
4
|
|
5
|
-
initializer
|
5
|
+
initializer "pg_saurus" do
|
6
6
|
ActiveSupport.on_load(:active_record) do
|
7
7
|
# load monkey patches
|
8
|
-
|
9
|
-
|
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
|
-
#
|
39
|
-
#
|
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
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
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 << ":
|
40
|
+
parts << "exclude_index: true"
|
41
41
|
|
42
42
|
" #{parts.join(', ')}"
|
43
43
|
end
|
data/lib/pg_saurus/tools.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
30
|
-
|
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
|
-
|
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.
|
data/lib/pg_saurus/version.rb
CHANGED
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:
|
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-
|
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: '
|
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: '
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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/
|
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
|