pg_power 1.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.
- data/README.markdown +212 -0
- data/lib/core_ext/active_record/connection_adapters/abstract/schema_statements.rb +139 -0
- data/lib/core_ext/active_record/connection_adapters/postgresql_adapter.rb +135 -0
- data/lib/core_ext/active_record/schema_dumper.rb +40 -0
- data/lib/pg_power.rb +16 -0
- data/lib/pg_power/connection_adapters.rb +9 -0
- data/lib/pg_power/connection_adapters/abstract_adapter.rb +20 -0
- data/lib/pg_power/connection_adapters/abstract_adapter/comment_methods.rb +62 -0
- data/lib/pg_power/connection_adapters/abstract_adapter/foreigner_methods.rb +67 -0
- data/lib/pg_power/connection_adapters/abstract_adapter/index_methods.rb +6 -0
- data/lib/pg_power/connection_adapters/abstract_adapter/schema_methods.rb +18 -0
- data/lib/pg_power/connection_adapters/foreign_key_definition.rb +5 -0
- data/lib/pg_power/connection_adapters/index_definition.rb +6 -0
- data/lib/pg_power/connection_adapters/postgresql_adapter.rb +16 -0
- data/lib/pg_power/connection_adapters/postgresql_adapter/comment_methods.rb +79 -0
- data/lib/pg_power/connection_adapters/postgresql_adapter/foreigner_methods.rb +190 -0
- data/lib/pg_power/connection_adapters/postgresql_adapter/index_methods.rb +42 -0
- data/lib/pg_power/connection_adapters/postgresql_adapter/schema_methods.rb +22 -0
- data/lib/pg_power/connection_adapters/table.rb +17 -0
- data/lib/pg_power/connection_adapters/table/comment_methods.rb +58 -0
- data/lib/pg_power/connection_adapters/table/foreigner_methods.rb +51 -0
- data/lib/pg_power/engine.rb +46 -0
- data/lib/pg_power/migration.rb +4 -0
- data/lib/pg_power/migration/command_recorder.rb +13 -0
- data/lib/pg_power/migration/command_recorder/comment_methods.rb +52 -0
- data/lib/pg_power/migration/command_recorder/foreigner_methods.rb +29 -0
- data/lib/pg_power/migration/command_recorder/schema_methods.rb +39 -0
- data/lib/pg_power/schema_dumper.rb +21 -0
- data/lib/pg_power/schema_dumper/comment_methods.rb +36 -0
- data/lib/pg_power/schema_dumper/foreigner_methods.rb +58 -0
- data/lib/pg_power/schema_dumper/schema_methods.rb +51 -0
- data/lib/pg_power/tools.rb +56 -0
- data/lib/pg_power/version.rb +4 -0
- data/lib/tasks/pg_power_tasks.rake +4 -0
- metadata +213 -0
data/README.markdown
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
# PgPower
|
2
|
+
|
3
|
+
ActiveRecord extension to get more from PostgreSQL:
|
4
|
+
|
5
|
+
* Create/drop schemas.
|
6
|
+
* Set/remove comments on columns and tables.
|
7
|
+
* Use foreign keys.
|
8
|
+
* Use partial indexes.
|
9
|
+
|
10
|
+
## Environment notes
|
11
|
+
|
12
|
+
It was tested with Rails 3.1.x and 3.2.x, Ruby 1.8.7 REE and 1.9.3.
|
13
|
+
|
14
|
+
|
15
|
+
## Schemas
|
16
|
+
|
17
|
+
### Create schema
|
18
|
+
|
19
|
+
In migrations you can use `create_schema` and `drop_schema` methods like this:
|
20
|
+
|
21
|
+
class ReplaceDemographySchemaWithPolitics < ActiveRecord::Migration
|
22
|
+
def change
|
23
|
+
drop_schema 'demography'
|
24
|
+
create_schema 'politics'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
### Create table
|
29
|
+
|
30
|
+
Use schema `:schema` option to specify schema name:
|
31
|
+
|
32
|
+
create_table "countries", :schema => "demography" do |t|
|
33
|
+
# columns goes here
|
34
|
+
end
|
35
|
+
|
36
|
+
### Move table to another schema
|
37
|
+
|
38
|
+
Move table `countries` from `demography` schema to `public`:
|
39
|
+
|
40
|
+
move_table_to_schema 'demography.countries', :public
|
41
|
+
|
42
|
+
## Table and column comments
|
43
|
+
|
44
|
+
Provides the following methods to manage comments:
|
45
|
+
|
46
|
+
* set\_table\_comment(table\_name, comment)
|
47
|
+
* remove\_table\_comment(table\_name)
|
48
|
+
* set\_column\_comment(table\_name, column\_name, comment)
|
49
|
+
* remove\_column\_comment(table\_name, column\_name, comment)
|
50
|
+
* set\_column\_comments(table\_name, comments)
|
51
|
+
* remove\_column\_comments(table\_name, *comments)
|
52
|
+
|
53
|
+
|
54
|
+
### Examples
|
55
|
+
|
56
|
+
Set a comment on the given table.
|
57
|
+
|
58
|
+
set_table_comment :phone_numbers, 'This table stores phone numbers that conform to the North American Numbering Plan.'
|
59
|
+
|
60
|
+
Sets a comment on a given column of a given table.
|
61
|
+
|
62
|
+
set_column_comment :phone_numbers, :npa, 'Numbering Plan Area Code - Allowed ranges: [2-9] for first digit, [0-9] for second and third digit.'
|
63
|
+
|
64
|
+
Removes any comment from the given table.
|
65
|
+
|
66
|
+
remove_table_comment :phone_numbers
|
67
|
+
|
68
|
+
Removes any comment from the given column of a given table.
|
69
|
+
|
70
|
+
remove_column_comment :phone_numbers, :npa
|
71
|
+
|
72
|
+
Set comments on multiple columns in the table.
|
73
|
+
|
74
|
+
set_column_comments :phone_numbers, :npa => 'Numbering Plan Area Code - Allowed ranges: [2-9] for first digit, [0-9] for second and third digit.',
|
75
|
+
:nxx => 'Central Office Number'
|
76
|
+
|
77
|
+
Remove comments from multiple columns in the table.
|
78
|
+
|
79
|
+
remove_column_comments :phone_numbers, :npa, :nxx
|
80
|
+
|
81
|
+
PgPower also adds extra methods to change_table.
|
82
|
+
|
83
|
+
Set comments:
|
84
|
+
|
85
|
+
change_table :phone_numbers do |t|
|
86
|
+
t.set_table_comment 'This table stores phone numbers that conform to the North American Numbering Plan.'
|
87
|
+
t.set_column_comment :npa, 'Numbering Plan Area Code - Allowed ranges: [2-9] for first digit, [0-9] for second and third digit.'
|
88
|
+
end
|
89
|
+
|
90
|
+
change_table :phone_numbers do |t|
|
91
|
+
t.set_column_comments :npa => 'Numbering Plan Area Code - Allowed ranges: [2-9] for first digit, [0-9] for second and third digit.',
|
92
|
+
:nxx => 'Central Office Number'
|
93
|
+
end
|
94
|
+
|
95
|
+
Remove comments:
|
96
|
+
|
97
|
+
change_table :phone_numbers do |t|
|
98
|
+
t.remove_table_comment
|
99
|
+
t.remove_column_comment :npa
|
100
|
+
end
|
101
|
+
|
102
|
+
change_table :phone_numbers do |t|
|
103
|
+
t.remove_column_comments :npa, :nxx
|
104
|
+
end
|
105
|
+
|
106
|
+
## Foreign keys
|
107
|
+
|
108
|
+
We imported some code of [foreigner](https://github.com/matthuhiggins/foreigner)
|
109
|
+
gem and patched it to be schema-aware. We also added support for index auto-generation.
|
110
|
+
|
111
|
+
You should disable `foreigner` in your Gemfile if you want to use `pg_power`.
|
112
|
+
|
113
|
+
If you do not want to generate an index, pass the :exclude_index => true option.
|
114
|
+
|
115
|
+
The syntax is compatible with `foreigner`:
|
116
|
+
|
117
|
+
|
118
|
+
Add foreign key from `comments` to `posts` using `post_id` column as key by default:
|
119
|
+
add_foreign_key(:comments, :posts)
|
120
|
+
|
121
|
+
Specify key explicitly:
|
122
|
+
add_foreign_key(:comments, :posts, :column => :blog_post_id)
|
123
|
+
|
124
|
+
Specify name of foreign key constraint:
|
125
|
+
add_foreign_key(:comments, :posts, :name => "comments_posts_fk")
|
126
|
+
|
127
|
+
It works with schemas as expected:
|
128
|
+
add_foreign_key('blog.comments', 'blog.posts')
|
129
|
+
|
130
|
+
Adds the index 'index_comments_on_post_id':
|
131
|
+
add_foreign_key(:comments, :posts)
|
132
|
+
|
133
|
+
Does not add an index:
|
134
|
+
add_foreign_key(:comments, :posts, :exclude_index => true)
|
135
|
+
|
136
|
+
## Partial Indexes
|
137
|
+
|
138
|
+
We used a Rails 4.x [pull request](https://github.com/rails/rails/pull/4956) as a
|
139
|
+
starting point, backported to Rails 3.1.x and patched it to be schema-aware.
|
140
|
+
|
141
|
+
### Examples
|
142
|
+
|
143
|
+
Add a partial index to a table
|
144
|
+
|
145
|
+
add_index(:comments, [:country_id, :user_id], :where => 'active')
|
146
|
+
|
147
|
+
Add a partial index to a schema table
|
148
|
+
|
149
|
+
add_index('blog.comments', :user_id, :where => 'active')
|
150
|
+
|
151
|
+
## Indexes on Expressions
|
152
|
+
|
153
|
+
PostgreSQL supports indexes on expressions. Right now, only basic functional
|
154
|
+
expressions are supported.
|
155
|
+
|
156
|
+
### Examples
|
157
|
+
|
158
|
+
Add an index to a column with a function
|
159
|
+
|
160
|
+
add_index(:comments, "lower(text)")
|
161
|
+
|
162
|
+
## Tools
|
163
|
+
|
164
|
+
PgPower::Tools provides number of useful methods:
|
165
|
+
|
166
|
+
PgPower::Tools.create_schema "services" # => create new PG schema "services"
|
167
|
+
PgPower::Tools.create_schema "nets" # => create new PG schema "nets"
|
168
|
+
PgPower::Tools.drop_schema "services" # => remove the PG schema "services"
|
169
|
+
PgPower::Tools.schemas # => ["public", "information_schema", "nets"]
|
170
|
+
PgPower::Tools.index_exists?(table, columns, options) # => returns true if an index exists for the given params
|
171
|
+
|
172
|
+
## Running tests:
|
173
|
+
|
174
|
+
* Configure `spec/dummy/config/database.yml` for development and test environments.
|
175
|
+
* Run `rake spec`.
|
176
|
+
* Make sure migrations don't raise exceptions and all specs pass.
|
177
|
+
|
178
|
+
## TODO:
|
179
|
+
|
180
|
+
Add next syntax to create table:
|
181
|
+
|
182
|
+
create_table "table_name", :schema => "schema_name" do |t|
|
183
|
+
# columns goes here
|
184
|
+
end
|
185
|
+
|
186
|
+
Support for JRuby:
|
187
|
+
|
188
|
+
* Jdbc driver provides its own `create_schema(schema, user)` method - solve conflicts.
|
189
|
+
|
190
|
+
## Credits
|
191
|
+
|
192
|
+
* [Potapov Sergey](https://github.com/greyblake) - schema support
|
193
|
+
* [Arthur Shagall](https://github.com/albertosaurus) - thanks for [pg_comment](https://github.com/albertosaurus/pg_comment)
|
194
|
+
* [Matthew Higgins](https://github.com/matthuhiggins) - thanks for [foreigner](https://github.com/matthuhiggins/foreigner), which was used as a base for the foreign key support
|
195
|
+
* [Marcelo Silveira](https://github.com/mhfs) - thanks for rails partial index support that was backported into this gem
|
196
|
+
|
197
|
+
## Copyright and License
|
198
|
+
|
199
|
+
Copyright (c) 2012 TMX Credit.
|
200
|
+
Initial foreign key code taken from foreigner, Copyright (c) 2009 Matthew Higgins
|
201
|
+
pg_comment Copyright (c) 2011 Arthur Shagall
|
202
|
+
Partial index Copyright (c) 2012 Marcelo Silveira
|
203
|
+
|
204
|
+
Released under the MIT License. See the MIT-LICENSE file for more details.
|
205
|
+
|
206
|
+
## Contributing
|
207
|
+
|
208
|
+
Contributions are welcome. However, before issuing a pull request, please make sure of the following:
|
209
|
+
|
210
|
+
* All specs are passing (under both ree and 1.9.3)
|
211
|
+
* Any new features have test coverage.
|
212
|
+
* Anything that breaks backward compatibility has a very good reason for doing so.
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters # :nodoc:
|
3
|
+
module SchemaStatements # :nodoc:
|
4
|
+
# Regexp used to find the function name and function argument of a
|
5
|
+
# function call
|
6
|
+
FUNCTIONAL_INDEX_REGEXP = /(\w+)\((\w+)\)/
|
7
|
+
|
8
|
+
# Adds a new index to the table. +column_name+ can be a single Symbol, or
|
9
|
+
# an Array of Symbols.
|
10
|
+
#
|
11
|
+
# ====== Creating a partial index
|
12
|
+
# add_index(:accounts, [:branch_id, :party_id], :unique => true, :where => "active")
|
13
|
+
# generates
|
14
|
+
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
|
15
|
+
#
|
16
|
+
def add_index(table_name, column_name, options = {})
|
17
|
+
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
|
18
|
+
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Checks to see if an index exists on a table for a given index definition.
|
22
|
+
#
|
23
|
+
# === Examples
|
24
|
+
# # Check that a partial index exists
|
25
|
+
# index_exists?(:suppliers, :company_id, :where => 'active')
|
26
|
+
#
|
27
|
+
# # GIVEN: "index_suppliers_on_company_id" UNIQUE, btree (company_id) WHERE active
|
28
|
+
# index_exists?(:suppliers, :company_id, :unique => true, :where => 'active') => true
|
29
|
+
# index_exists?(:suppliers, :company_id, :unique => true) => false
|
30
|
+
#
|
31
|
+
def index_exists?(table_name, column_name, options = {})
|
32
|
+
column_names = Array.wrap(column_name)
|
33
|
+
index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
|
34
|
+
|
35
|
+
# Always compare the index name
|
36
|
+
default_comparator = lambda { |index| index.name == index_name }
|
37
|
+
comparators = [default_comparator]
|
38
|
+
|
39
|
+
# Add a comparator for each index option that is part of the query
|
40
|
+
index_options = [:unique, :where]
|
41
|
+
index_options.each do |index_option|
|
42
|
+
comparators << if options.key?(index_option)
|
43
|
+
lambda do |index|
|
44
|
+
pg_where_clause = index.send(index_option)
|
45
|
+
# pg does nothing to boolean clauses, e.g. 'where active' => 'where active'
|
46
|
+
if pg_where_clause.is_a?(TrueClass) or pg_where_clause.is_a?(FalseClass)
|
47
|
+
pg_where_clause == options[index_option]
|
48
|
+
else
|
49
|
+
# pg adds parentheses around non-boolean clauses, e.g. 'where color IS NULL' => 'where (color is NULL)'
|
50
|
+
pg_where_clause.gsub!(/[()]/,'')
|
51
|
+
# pg casts string comparison ::text. e.g. "where color = 'black'" => "where ((color)::text = 'black'::text)"
|
52
|
+
pg_where_clause.gsub!(/::text/,'')
|
53
|
+
# prevent case from impacting the comparison
|
54
|
+
pg_where_clause.downcase == options[index_option].downcase
|
55
|
+
end
|
56
|
+
end
|
57
|
+
else
|
58
|
+
# If the given index_option is not an argument to the index_exists? query,
|
59
|
+
# select only those pg indexes that do not have the component
|
60
|
+
lambda { |index| index.send(index_option).blank? }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Search all indexes for any that match all comparators
|
65
|
+
indexes(table_name).any? do |index|
|
66
|
+
comparators.inject(true) { |ret, comparator| ret && comparator.call(index) }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Derives the name of the index from the given table name and options hash.
|
71
|
+
def index_name(table_name, options) #:nodoc:
|
72
|
+
if Hash === options # legacy support
|
73
|
+
if options[:column]
|
74
|
+
column_names = Array.wrap(options[:column]).map {|c| expression_index_name(c)}
|
75
|
+
"index_#{table_name}_on_#{column_names * '_and_'}"
|
76
|
+
elsif options[:name]
|
77
|
+
options[:name]
|
78
|
+
else
|
79
|
+
raise ArgumentError, "You must specify the index name"
|
80
|
+
end
|
81
|
+
else
|
82
|
+
index_name(table_name, :column => options)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns options used to build out index SQL
|
87
|
+
#
|
88
|
+
# Added support for partial indexes implemented using the :where option
|
89
|
+
#
|
90
|
+
def add_index_options(table_name, column_name, options = {})
|
91
|
+
column_names = Array(column_name)
|
92
|
+
index_name = index_name(table_name, :column => column_names)
|
93
|
+
|
94
|
+
if Hash === options # legacy support, since this param was a string
|
95
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
96
|
+
index_name = options[:name].to_s if options.key?(:name)
|
97
|
+
if supports_partial_index?
|
98
|
+
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
|
99
|
+
end
|
100
|
+
else
|
101
|
+
index_type = options
|
102
|
+
end
|
103
|
+
|
104
|
+
if index_name.length > index_name_length
|
105
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
|
106
|
+
end
|
107
|
+
if index_name_exists?(table_name, index_name, false)
|
108
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
|
109
|
+
end
|
110
|
+
index_columns = quoted_columns_for_index(column_names, options).join(", ")
|
111
|
+
|
112
|
+
[index_name, index_type, index_columns, index_options]
|
113
|
+
end
|
114
|
+
protected :add_index_options
|
115
|
+
|
116
|
+
# Override super method to provide support for expression column names
|
117
|
+
def quoted_columns_for_index(column_names, options = {})
|
118
|
+
column_names.map do |name|
|
119
|
+
if name =~ FUNCTIONAL_INDEX_REGEXP
|
120
|
+
"#{$1}(#{quote_column_name($2)})"
|
121
|
+
else
|
122
|
+
quote_column_name(name)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
protected :quoted_columns_for_index
|
127
|
+
|
128
|
+
# Map an expression to a name appropriate for an index
|
129
|
+
def expression_index_name(column_name)
|
130
|
+
if column_name =~ FUNCTIONAL_INDEX_REGEXP
|
131
|
+
"#{$1.downcase}_#{$2}"
|
132
|
+
else
|
133
|
+
column_name
|
134
|
+
end
|
135
|
+
end
|
136
|
+
private :expression_index_name
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module ActiveRecord # :nodoc:
|
2
|
+
module ConnectionAdapters # :nodoc:
|
3
|
+
# Patched version: 3.1.3
|
4
|
+
# Patched methods::
|
5
|
+
# * indexes
|
6
|
+
class PostgreSQLAdapter
|
7
|
+
# In Rails3.2 method #extract_schema_and_table is moved into Utils module.
|
8
|
+
# In Rails3.1 it's implemented right in PostgreSQLAdapter class.
|
9
|
+
# So it's Rails3.2 we include the module into PostgreSQLAdapter in order to make
|
10
|
+
# it compatible to Rails3.1
|
11
|
+
# -- sergey.potapov 2012-06-25
|
12
|
+
if ActiveRecord::VERSION::STRING =~ /^3\.2/
|
13
|
+
include self::Utils
|
14
|
+
end
|
15
|
+
|
16
|
+
# Regex to find columns used in index statements
|
17
|
+
INDEX_COLUMN_EXPRESSION = /ON \w+(?: USING \w+ )?\((.+)\)/
|
18
|
+
# Regex to find where clause in index statements
|
19
|
+
INDEX_WHERE_EXPRESION = /WHERE (.+)$/
|
20
|
+
|
21
|
+
# Returns an array of indexes for the given table.
|
22
|
+
#
|
23
|
+
# == Patch 1 reason:
|
24
|
+
# Since {ActiveRecord::SchemaDumper#tables} is patched to process tables
|
25
|
+
# with a schema prefix, the {#indexes} method receives table_name as
|
26
|
+
# "<schema>.<table>". This patch allows it to handle table names with
|
27
|
+
# a schema prefix.
|
28
|
+
#
|
29
|
+
# == Patch 1:
|
30
|
+
# Search using provided schema if table_name includes schema name.
|
31
|
+
#
|
32
|
+
# == Patch 2 reason:
|
33
|
+
# {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#indexes} is patched
|
34
|
+
# to support partial indexes using :where clause.
|
35
|
+
#
|
36
|
+
# == Patch 2:
|
37
|
+
# Search the postgres indexdef for the where clause and pass the output to
|
38
|
+
# the custom {PgPower::ConnectionAdapters::IndexDefinition}
|
39
|
+
#
|
40
|
+
def indexes(table_name, name = nil)
|
41
|
+
schema, table = extract_schema_and_table(table_name)
|
42
|
+
schemas = schema ? "ARRAY['#{schema}']" : 'current_schemas(false)'
|
43
|
+
|
44
|
+
result = query(<<-SQL, name)
|
45
|
+
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
|
46
|
+
FROM pg_class t
|
47
|
+
INNER JOIN pg_index d ON t.oid = d.indrelid
|
48
|
+
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
49
|
+
WHERE i.relkind = 'i'
|
50
|
+
AND d.indisprimary = 'f'
|
51
|
+
AND t.relname = '#{table}'
|
52
|
+
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (#{schemas}) )
|
53
|
+
ORDER BY i.relname
|
54
|
+
SQL
|
55
|
+
|
56
|
+
result.map do |row|
|
57
|
+
index = {
|
58
|
+
:name => row[0],
|
59
|
+
:unique => row[1] == 't',
|
60
|
+
:keys => row[2].split(" "),
|
61
|
+
:definition => row[3],
|
62
|
+
:id => row[4]
|
63
|
+
}
|
64
|
+
|
65
|
+
column_names = find_column_names(table_name, index)
|
66
|
+
|
67
|
+
unless column_names.empty?
|
68
|
+
where = find_where_statement(index)
|
69
|
+
lengths = find_lengths(index)
|
70
|
+
|
71
|
+
PgPower::ConnectionAdapters::IndexDefinition.new(table_name, index[:name], index[:unique], column_names, lengths, where)
|
72
|
+
end
|
73
|
+
end.compact
|
74
|
+
end
|
75
|
+
|
76
|
+
# Find column names from index attributes. If the columns are virtual (ie
|
77
|
+
# this is an expression index) then it will try to return the functions
|
78
|
+
# that represent each column
|
79
|
+
#
|
80
|
+
# @param [String] table_name the name of the table
|
81
|
+
# @param [Hash] index index attributes
|
82
|
+
# @return [Array]
|
83
|
+
def find_column_names(table_name, index)
|
84
|
+
columns = Hash[query(<<-SQL, "Columns for index #{index[:name]} on #{table_name}")]
|
85
|
+
SELECT a.attnum, a.attname
|
86
|
+
FROM pg_attribute a
|
87
|
+
WHERE a.attrelid = #{index[:id]}
|
88
|
+
AND a.attnum IN (#{index[:keys].join(",")})
|
89
|
+
SQL
|
90
|
+
|
91
|
+
column_names = columns.values_at(*index[:keys]).compact
|
92
|
+
|
93
|
+
if column_names.empty?
|
94
|
+
definition = index[:definition].sub(INDEX_WHERE_EXPRESION, '')
|
95
|
+
if column_expression = definition.match(INDEX_COLUMN_EXPRESSION)[1]
|
96
|
+
column_names = column_expression.split(',').map do |functional_name|
|
97
|
+
remove_type(functional_name)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
column_names
|
103
|
+
end
|
104
|
+
|
105
|
+
# Find where statement from index definition
|
106
|
+
#
|
107
|
+
# @param [Hash] index index attributes
|
108
|
+
# @return [String] where statement
|
109
|
+
def find_where_statement(index)
|
110
|
+
index[:definition].scan(INDEX_WHERE_EXPRESION).flatten[0]
|
111
|
+
end
|
112
|
+
|
113
|
+
# Find length of index
|
114
|
+
# TODO Update lengths once we merge in ActiveRecord code that supports it. -dresselm 20120305
|
115
|
+
#
|
116
|
+
# @param [Hash] index index attributes
|
117
|
+
# @return [Array]
|
118
|
+
def find_lengths(index)
|
119
|
+
[]
|
120
|
+
end
|
121
|
+
|
122
|
+
# Remove type specification from stored Postgres index definitions
|
123
|
+
#
|
124
|
+
# @param [String] column_with_type the name of the column with type
|
125
|
+
# @return [String]
|
126
|
+
#
|
127
|
+
# @example
|
128
|
+
# remove_type("((col)::text")
|
129
|
+
# => "col"
|
130
|
+
def remove_type(column_with_type)
|
131
|
+
column_with_type.sub(/\((\w+)\)::\w+/, '\1')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|