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 +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
|