schema_plus_pg_indexes 0.1.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 +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +17 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +76 -0
- data/Rakefile +9 -0
- data/gemfiles/Gemfile.base +8 -0
- data/gemfiles/rails-4.2/Gemfile.base +3 -0
- data/gemfiles/rails-4.2/Gemfile.postgresql +10 -0
- data/lib/schema_plus_pg_indexes.rb +11 -0
- data/lib/schema_plus_pg_indexes/active_record/connection_adapters/index_definition.rb +49 -0
- data/lib/schema_plus_pg_indexes/middleware/migration.rb +23 -0
- data/lib/schema_plus_pg_indexes/middleware/postgresql/dumper.rb +55 -0
- data/lib/schema_plus_pg_indexes/middleware/postgresql/migration.rb +83 -0
- data/lib/schema_plus_pg_indexes/middleware/postgresql/query.rb +130 -0
- data/lib/schema_plus_pg_indexes/version.rb +3 -0
- data/schema_dev.yml +7 -0
- data/schema_plus_pg_indexes.gemspec +30 -0
- data/spec/deprecation_spec.rb +62 -0
- data/spec/index_definition_spec.rb +135 -0
- data/spec/index_spec.rb +121 -0
- data/spec/named_schema_spec.rb +60 -0
- data/spec/schema_dumper_spec.rb +166 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/support/matchers/have_index.rb +60 -0
- metadata +203 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1c7a0890c1692f1bccb36a22dd4c68c81385e2fa
|
4
|
+
data.tar.gz: 6aefdddf74cca2fd8f72120b2098b674a69ae97d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b4418c479638217697120b8278121e7342da4eb4595c2af12807631f9ea5c60bed98645aeb02fe83973865a707159c10d2c3dbf38702642eeb06bab5eb4968bf
|
7
|
+
data.tar.gz: fb4644fab3314297ad7ed94487006156630009d395cde790057a00c3818f942193a638de7dd4d53caf0cb77705d6d60367bb4a5b69f7a3aa9b73b583be6ab4ee
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was auto-generated by the schema_dev tool, based on the data in
|
2
|
+
# ./schema_dev.yml
|
3
|
+
# Please do not edit this file; any changes will be overwritten next time
|
4
|
+
# schema_dev gets run.
|
5
|
+
---
|
6
|
+
sudo: false
|
7
|
+
rvm:
|
8
|
+
- 1.9.3
|
9
|
+
- 2.1.5
|
10
|
+
gemfile:
|
11
|
+
- gemfiles/rails-4.2/Gemfile.postgresql
|
12
|
+
env: POSTGRESQL_DB_USER=postgres
|
13
|
+
addons:
|
14
|
+
postgresql: '9.3'
|
15
|
+
before_script: bundle exec rake create_databases
|
16
|
+
after_script: bundle exec rake drop_databases
|
17
|
+
script: bundle exec rake travis
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 ronen barzel
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
[](http://badge.fury.io/rb/schema_plus_pg_indexes)
|
2
|
+
[](http://travis-ci.org/SchemaPlus/schema_plus_pg_indexes)
|
3
|
+
[](https://coveralls.io/r/SchemaPlus/schema_plus_pg_indexes)
|
4
|
+
[](https://gemnasium.com/SchemaPlus/schema_plus_pg_indexes)
|
5
|
+
|
6
|
+
# schema_plus_pg_indexes
|
7
|
+
|
8
|
+
Schema_plus_pg_indexes adds into `ActiveRecord` support for some additional PostgreSQL index features: expressions, operator classes, and case-insensitive indexes:
|
9
|
+
|
10
|
+
t.string :last_name, index: { expression: 'upper(last_name)' }
|
11
|
+
t.string :last_name, index: { operator_class: 'varchar_pattern_ops' }
|
12
|
+
t.string :last_name, index: { with: :address, operator_class: {last_name: 'varchar_pattern_ops', address: 'text_pattern_ops' }
|
13
|
+
t.string :last_name, index: { case_sensitive: false }
|
14
|
+
|
15
|
+
t.index expression: 'upper(last_name)', name: 'my_index' # no column given, must give a name
|
16
|
+
|
17
|
+
Case insensitivity is a shorthand for the expression `lower(last_name)`
|
18
|
+
|
19
|
+
The `ActiveRecord::ConnectionAdapters::IndexDefinition` object has the corresponding methods defined on it: `#expression`, `#operator_classes` and `#case_sensitive?`
|
20
|
+
|
21
|
+
Schema_plus_pg_indexes is part of the [SchemaPlus](https://github.com/SchemaPlus/) family of Ruby on Rails extension gems.
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
In your application's Gemfile
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
gem "schema_plus_pg_indexes"
|
29
|
+
```
|
30
|
+
## Compatibility
|
31
|
+
|
32
|
+
schema_plus_pg_indexes is tested on
|
33
|
+
|
34
|
+
[//]: # SCHEMA_DEV: MATRIX - begin
|
35
|
+
[//]: # These lines are auto-generated by schema_dev based on schema_dev.yml
|
36
|
+
* ruby **1.9.3** with rails **4.2**, using **postgresql**
|
37
|
+
* ruby **2.1.5** with rails **4.2**, using **postgresql**
|
38
|
+
|
39
|
+
[//]: # SCHEMA_DEV: MATRIX - end
|
40
|
+
|
41
|
+
|
42
|
+
## History
|
43
|
+
|
44
|
+
### v0.1.0
|
45
|
+
|
46
|
+
* Initial release
|
47
|
+
|
48
|
+
## Development & Testing
|
49
|
+
|
50
|
+
Are you interested in contributing to schema_plus_pg_indexes? Thanks! Please follow
|
51
|
+
the standard protocol: fork, feature branch, develop, push, and issue pull request.
|
52
|
+
|
53
|
+
Some things to know about to help you develop and test:
|
54
|
+
|
55
|
+
* **schema_dev**: schema_plus_pg_indexes uses [schema_dev](https://github.com/SchemaPlus/schema_dev) to
|
56
|
+
facilitate running rspec tests on the matrix of ruby, rails, and database
|
57
|
+
versions that the gem supports, both locally and on
|
58
|
+
[travis-ci](http://travis-ci.org/SchemaPlus/schema_plus_pg_indexes)
|
59
|
+
|
60
|
+
To to run rspec locally on the full matrix, do:
|
61
|
+
|
62
|
+
$ schema_dev bundle install
|
63
|
+
$ schema_dev rspec
|
64
|
+
|
65
|
+
You can also run on just one configuration at a time; For info, see `schema_dev --help` or the
|
66
|
+
[schema_dev](https://github.com/SchemaPlus/schema_dev) README.
|
67
|
+
|
68
|
+
The matrix of configurations is specified in `schema_dev.yml` in
|
69
|
+
the project root.
|
70
|
+
|
71
|
+
* **schema_monkey**: schema_plus_pg_indexes extends ActiveRecord using
|
72
|
+
[schema_monkey](https://github.com/SchemaPlus/schema_monkey)'s extension
|
73
|
+
API and protocols -- see its README for details. If your contribution needs any additional monkey patching
|
74
|
+
that isn't already supported by
|
75
|
+
[schema_monkey](https://github.com/SchemaPlus/schema_monkey), please head
|
76
|
+
over there and submit a PR.
|
data/Rakefile
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
gemspec :path => File.expand_path('..', __FILE__)
|
3
|
+
|
4
|
+
platform :ruby do
|
5
|
+
gem "byebug" if RUBY_VERSION > "2"
|
6
|
+
end
|
7
|
+
|
8
|
+
File.exist?(gemfile_local = File.expand_path('../Gemfile.local', __FILE__)) and eval File.read(gemfile_local), binding, gemfile_local
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'schema_monkey'
|
2
|
+
require 'schema_plus_indexes'
|
3
|
+
|
4
|
+
require_relative 'schema_plus_pg_indexes/active_record/connection_adapters/index_definition'
|
5
|
+
require_relative 'schema_plus_pg_indexes/middleware/migration'
|
6
|
+
require_relative 'schema_plus_pg_indexes/middleware/postgresql/dumper'
|
7
|
+
require_relative 'schema_plus_pg_indexes/middleware/postgresql/migration'
|
8
|
+
require_relative 'schema_plus_pg_indexes/middleware/postgresql/query'
|
9
|
+
require_relative 'schema_plus_pg_indexes/version'
|
10
|
+
|
11
|
+
SchemaMonkey.register(SchemaPlusPgIndexes)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module SchemaPlusPgIndexes
|
2
|
+
module ActiveRecord
|
3
|
+
module ConnectionAdapters
|
4
|
+
#
|
5
|
+
# SchemaPlusPgIndexes extends the IndexDefinition object to return information
|
6
|
+
# case sensitivity, expessions, and operator classes
|
7
|
+
module IndexDefinition
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.alias_method_chain :initialize, :schema_plus_pg_indexes
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :expression
|
14
|
+
attr_accessor :operator_classes
|
15
|
+
|
16
|
+
def case_sensitive?
|
17
|
+
@case_sensitive
|
18
|
+
end
|
19
|
+
|
20
|
+
def conditions
|
21
|
+
ActiveSupport::Deprecation.warn "ActiveRecord IndexDefinition#conditions is deprecated, used #where instead"
|
22
|
+
where
|
23
|
+
end
|
24
|
+
|
25
|
+
def kind
|
26
|
+
ActiveSupport::Deprecation.warn "ActiveRecord IndexDefinition#kind is deprecated, used #using instead"
|
27
|
+
using
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize_with_schema_plus_pg_indexes(*args)
|
31
|
+
initialize_without_schema_plus_pg_indexes(*args)
|
32
|
+
options = args.dup.extract_options!
|
33
|
+
@expression = options[:expression]
|
34
|
+
@operator_classes = options[:operator_classes] || {}
|
35
|
+
@case_sensitive = options.include?(:case_sensitive) ? options[:case_sensitive] : true
|
36
|
+
end
|
37
|
+
|
38
|
+
def ==(other)
|
39
|
+
return false if not super(other) # can use super here because == is defined in SchemaPlusIndexes which was included before us
|
40
|
+
return false unless self.expression == other.expression
|
41
|
+
return false unless !!self.case_sensitive? == !!other.case_sensitive?
|
42
|
+
return false unless self.operator_classes == other.operator_classes
|
43
|
+
return true
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SchemaPlusPgIndexes
|
2
|
+
module Middleware
|
3
|
+
module Migration
|
4
|
+
|
5
|
+
def self.insert
|
6
|
+
SchemaMonkey::Middleware::Migration::Index.prepend DeprecateArgs
|
7
|
+
end
|
8
|
+
|
9
|
+
class DeprecateArgs < SchemaMonkey::Middleware::Base
|
10
|
+
def call(env)
|
11
|
+
{:conditions => :where, :kind => :using}.each do |deprecated, proper|
|
12
|
+
if env.options[deprecated]
|
13
|
+
ActiveSupport::Deprecation.warn "ActiveRecord index option #{deprecated.inspect} is deprecated, use #{proper.inspect} instead"
|
14
|
+
env.options[proper] = env.options.delete(deprecated)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
continue env
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module SchemaPlusPgIndexes
|
2
|
+
module Middleware
|
3
|
+
module Postgresql
|
4
|
+
module Dumper
|
5
|
+
|
6
|
+
def self.insert
|
7
|
+
SchemaMonkey::Middleware::Dumper::Indexes.append DumpExtensions
|
8
|
+
SchemaMonkey::Middleware::Dumper::Table.append InlineIndexes
|
9
|
+
end
|
10
|
+
|
11
|
+
class DumpExtensions < SchemaMonkey::Middleware::Base
|
12
|
+
def call(env)
|
13
|
+
continue env
|
14
|
+
|
15
|
+
index_defs = Dumper.get_index_defiinitions(env, env.table)
|
16
|
+
|
17
|
+
env.table.indexes.each do |index_dump|
|
18
|
+
index_def = index_defs.find(&its.name == index_dump.name)
|
19
|
+
if index_def.columns.blank?
|
20
|
+
index_dump.add_option "expression: #{index_def.expression.inspect}" if index_def.expression and index_def.columns.blank?
|
21
|
+
else
|
22
|
+
index_dump.add_option "case_sensitive: false" unless index_def.case_sensitive?
|
23
|
+
unless index_def.operator_classes.blank?
|
24
|
+
if index_def.operator_classes.values.uniq.length == 1
|
25
|
+
index_dump.add_option "operator_class: #{index_def.operator_classes.values.first.inspect}"
|
26
|
+
else
|
27
|
+
index_dump.add_option "operator_class: {" + index_def.operator_classes.map{|column, val| "#{column.inspect}=>#{val.inspect}"}.join(", ") + "}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class InlineIndexes < SchemaMonkey::Middleware::Base
|
36
|
+
def call(env)
|
37
|
+
continue env
|
38
|
+
|
39
|
+
index_defs = Dumper.get_index_defiinitions(env, env.table)
|
40
|
+
|
41
|
+
env.table.indexes.select(&its.columns.blank?).each do |index|
|
42
|
+
env.table.statements << "t.index name: #{index.name.inspect}, #{index.options}"
|
43
|
+
env.table.indexes.delete(index)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.get_index_defiinitions(env, table_dump)
|
49
|
+
env.dump.data.index_definitions ||= {}
|
50
|
+
env.dump.data.index_definitions[table_dump.name] ||= env.connection.indexes(table_dump.name)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module SchemaPlusPgIndexes
|
2
|
+
module Middleware
|
3
|
+
module Postgresql
|
4
|
+
module Migration
|
5
|
+
def self.insert
|
6
|
+
SchemaMonkey::Middleware::Migration::IndexComponentsSql.append DefineExtensions
|
7
|
+
end
|
8
|
+
|
9
|
+
class DefineExtensions < SchemaMonkey::Middleware::Base
|
10
|
+
# SchemaPlusPgIndexes provides the following extra options for PostgreSQL
|
11
|
+
# indexes:
|
12
|
+
# * +:expression+ - SQL expression to index. column_name can be nil or ommitted, in which case :name must be provided
|
13
|
+
# * +:operator_class+ - an operator class name or a hash mapping column name to operator class name
|
14
|
+
# * +:case_sensitive - setting to +false+ is a shorthand for :expression => 'LOWER(column_name)'
|
15
|
+
#
|
16
|
+
# The <tt>:case_sensitive => false</tt> option ties in with Rails built-in support for case-insensitive searching:
|
17
|
+
# validates_uniqueness_of :name, :case_sensitive => false
|
18
|
+
#
|
19
|
+
# Since since <tt>:case_sensitive => false</tt> is implemented by
|
20
|
+
# using <tt>:expression</tt>, this raises an ArgumentError if both
|
21
|
+
# are specified simultaneously.
|
22
|
+
#
|
23
|
+
def call(env)
|
24
|
+
options = env.options
|
25
|
+
column_names = env.column_names
|
26
|
+
table_name = env.table_name
|
27
|
+
connection = env.connection
|
28
|
+
|
29
|
+
if env.column_names.empty?
|
30
|
+
raise ArgumentError, "No columns and :expression missing from options - cannot create index" unless options[:expression]
|
31
|
+
raise ArgumentError, "No columns, and index name not given. Pass :name option" unless options[:name]
|
32
|
+
end
|
33
|
+
|
34
|
+
expression = options.delete(:expression)
|
35
|
+
operator_classes = options.delete(:operator_class)
|
36
|
+
case_insensitive = (options.delete(:case_sensitive) == false)
|
37
|
+
|
38
|
+
if expression
|
39
|
+
raise ArgumentError, "Cannot specify :case_sensitive => false with an expression. Use LOWER(column_name)" if case_insensitive
|
40
|
+
expression.strip!
|
41
|
+
if m = expression.match(/^using\s+(?<using>\S+)\s*(?<rest>.*)/i)
|
42
|
+
options[:using] = m[:using]
|
43
|
+
expression = m[:rest]
|
44
|
+
end
|
45
|
+
if m = expression.match(/^(?<rest>.*)\s+where\s+(?<where>.*)/i)
|
46
|
+
options[:where] = m[:where]
|
47
|
+
expression = m[:rest]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
continue env
|
52
|
+
|
53
|
+
if operator_classes and not operator_classes.is_a? Hash
|
54
|
+
operator_classes = Hash[column_names.map {|name| [name, operator_classes]}]
|
55
|
+
end
|
56
|
+
|
57
|
+
if expression
|
58
|
+
env.sql.columns = expression.sub(/ ^\( (.*) \) $/x, '\1')
|
59
|
+
elsif operator_classes or case_insensitive
|
60
|
+
option_strings = Hash[column_names.map {|name| [name, '']}]
|
61
|
+
(operator_classes||{}).stringify_keys.each do |column, opclass|
|
62
|
+
option_strings[column] += " #{opclass}" if opclass
|
63
|
+
end
|
64
|
+
option_strings = connection.send :add_index_sort_order, option_strings, column_names, options
|
65
|
+
|
66
|
+
if case_insensitive
|
67
|
+
caseable_columns = connection.columns(table_name).select { |col| [:string, :text].include?(col.type) }.map(&:name)
|
68
|
+
quoted_column_names = column_names.map do |col_name|
|
69
|
+
(caseable_columns.include?(col_name.to_s) ? "LOWER(#{connection.quote_column_name(col_name)})" : connection.quote_column_name(col_name)) + option_strings[col_name]
|
70
|
+
end
|
71
|
+
else
|
72
|
+
quoted_column_names = column_names.map { |col_name| connection.quote_column_name(col_name) + option_strings[col_name] }
|
73
|
+
end
|
74
|
+
|
75
|
+
env.sql.columns = quoted_column_names.join(', ')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module SchemaPlusPgIndexes
|
2
|
+
module Middleware
|
3
|
+
module Postgresql
|
4
|
+
module Query
|
5
|
+
def self.insert
|
6
|
+
SchemaMonkey::Middleware::Query::Indexes.append LookupExtensions
|
7
|
+
end
|
8
|
+
|
9
|
+
class LookupExtensions < SchemaMonkey::Middleware::Base
|
10
|
+
|
11
|
+
def get_opclass_names(env, opclasses)
|
12
|
+
@opclass_names ||= {}
|
13
|
+
if (missing = opclasses - @opclass_names.keys).any?
|
14
|
+
result = env.connection.query(<<-SQL, 'SCHEMA')
|
15
|
+
SELECT oid, opcname FROM pg_opclass
|
16
|
+
WHERE (NOT opcdefault) AND oid IN (#{opclasses.join(',')})
|
17
|
+
SQL
|
18
|
+
result.each do |oid, opcname|
|
19
|
+
@opclass_names[oid] = opcname
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(env)
|
25
|
+
# Ideally we'd let AR do its stuff and then add the extras.
|
26
|
+
#
|
27
|
+
# But one of the extras is expressions. AR completely strips out
|
28
|
+
# indexes with expressions, so to handle them we need to
|
29
|
+
# essentially reissue the original query and then duplicate what
|
30
|
+
# AR does to process them. That being the case we may as well
|
31
|
+
# just skip AR's implementation and use ours.
|
32
|
+
#
|
33
|
+
# We could limit that query to just those indexes that have
|
34
|
+
# expressions, but we'd still have our code duplicating the AR
|
35
|
+
# code. Plus, our own query can handle operator classess at the
|
36
|
+
# same time, but to add operator_classes to AR's definitions we'd
|
37
|
+
# still have to issue additional queries. Plus, using our own
|
38
|
+
# query we have the opportunity to handle tables of the form
|
39
|
+
# 'namespace.tablename'
|
40
|
+
#
|
41
|
+
# So, we use our code and DO NOT DO:
|
42
|
+
#
|
43
|
+
# continue env
|
44
|
+
#
|
45
|
+
result = env.connection.query(<<-SQL, 'SCHEMA')
|
46
|
+
|
47
|
+
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
|
48
|
+
m.amname, pg_get_expr(d.indpred, t.oid) as conditions, pg_get_expr(d.indexprs, t.oid) as expression,
|
49
|
+
d.indclass
|
50
|
+
FROM pg_class t
|
51
|
+
INNER JOIN pg_index d ON t.oid = d.indrelid
|
52
|
+
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
53
|
+
INNER JOIN pg_am m ON i.relam = m.oid
|
54
|
+
WHERE i.relkind = 'i'
|
55
|
+
AND d.indisprimary = 'f'
|
56
|
+
AND t.relname = '#{table_name_without_namespace(env.table_name)}'
|
57
|
+
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = #{namespace_sql(env.table_name)} )
|
58
|
+
ORDER BY i.relname
|
59
|
+
SQL
|
60
|
+
|
61
|
+
env.index_definitions += result.map do |(index_name, unique, indkey, inddef, oid, using, conditions, expression, indclass)|
|
62
|
+
index_keys = indkey.split(" ")
|
63
|
+
opclasses = indclass.split(" ")
|
64
|
+
|
65
|
+
rows = env.connection.query(<<-SQL, 'SCHEMA')
|
66
|
+
SELECT CAST(a.attnum as VARCHAR), a.attname, t.typname
|
67
|
+
FROM pg_attribute a
|
68
|
+
INNER JOIN pg_type t ON a.atttypid = t.oid
|
69
|
+
WHERE a.attrelid = #{oid}
|
70
|
+
SQL
|
71
|
+
columns = {}
|
72
|
+
types = {}
|
73
|
+
rows.each do |num, name, type|
|
74
|
+
columns[num] = name
|
75
|
+
types[name] = type
|
76
|
+
end
|
77
|
+
|
78
|
+
column_names = columns.values_at(*index_keys).compact
|
79
|
+
case_sensitive = true
|
80
|
+
|
81
|
+
# extract column names from the expression, for a
|
82
|
+
# case-insensitive index.
|
83
|
+
# only applies to character, character varying, and text
|
84
|
+
if expression
|
85
|
+
rexp_lower = %r{\blower\(\(?([^)]+)(\)::text)?\)}
|
86
|
+
if expression.match /\A#{rexp_lower}(?:, #{rexp_lower})*\z/
|
87
|
+
case_insensitive_columns = expression.scan(rexp_lower).map(&:first).select{|column| %W[char varchar text].include? types[column]}
|
88
|
+
if case_insensitive_columns.any?
|
89
|
+
case_sensitive = false
|
90
|
+
column_names = index_keys.map { |index_key|
|
91
|
+
index_key == '0' ? case_insensitive_columns.shift : columns[index_key]
|
92
|
+
}.compact
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
get_opclass_names(env, opclasses)
|
98
|
+
operator_classes = Hash[
|
99
|
+
index_keys.zip(opclasses).map { |index_key, opclass| [columns[index_key], @opclass_names[opclass]] }
|
100
|
+
]
|
101
|
+
operator_classes.delete_if{|k,v| v.nil?}
|
102
|
+
|
103
|
+
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
104
|
+
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
|
105
|
+
orders = desc_order_columns.any? ? Hash[column_names.map {|column| [column, desc_order_columns.include?(column) ? :desc : :asc]}] : {}
|
106
|
+
|
107
|
+
::ActiveRecord::ConnectionAdapters::IndexDefinition.new(env.table_name, column_names,
|
108
|
+
:name => index_name,
|
109
|
+
:unique => (unique == 't'),
|
110
|
+
:orders => orders,
|
111
|
+
:where => conditions,
|
112
|
+
:case_sensitive => case_sensitive,
|
113
|
+
:using => using.downcase == "btree" ? nil : using.to_sym,
|
114
|
+
:operator_classes => operator_classes,
|
115
|
+
:expression => expression)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def namespace_sql(table_name)
|
120
|
+
(table_name.to_s =~ /(.*)[.]/) ? "'#{$1}'" : "ANY (current_schemas(false))"
|
121
|
+
end
|
122
|
+
|
123
|
+
def table_name_without_namespace(table_name)
|
124
|
+
table_name.to_s.sub /.*[.]/, ''
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|