schema_plus_pg_indexes 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/schema_plus_pg_indexes.svg)](http://badge.fury.io/rb/schema_plus_pg_indexes)
|
2
|
+
[![Build Status](https://secure.travis-ci.org/SchemaPlus/schema_plus_pg_indexes.svg)](http://travis-ci.org/SchemaPlus/schema_plus_pg_indexes)
|
3
|
+
[![Coverage Status](https://img.shields.io/coveralls/SchemaPlus/schema_plus_pg_indexes.svg)](https://coveralls.io/r/SchemaPlus/schema_plus_pg_indexes)
|
4
|
+
[![Dependency Status](https://gemnasium.com/lomba/schema_plus_pg_indexes.svg)](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
|