pg_reindex 0.1.1

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/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *.swp
2
+ .bundle
3
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,47 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ pg_reindex (0.1.1)
5
+ pg
6
+ thor
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ activemodel (3.2.5)
12
+ activesupport (= 3.2.5)
13
+ builder (~> 3.0.0)
14
+ activerecord (3.2.5)
15
+ activemodel (= 3.2.5)
16
+ activesupport (= 3.2.5)
17
+ arel (~> 3.0.2)
18
+ tzinfo (~> 0.3.29)
19
+ activesupport (3.2.5)
20
+ i18n (~> 0.6)
21
+ multi_json (~> 1.0)
22
+ arel (3.0.2)
23
+ builder (3.0.0)
24
+ diff-lcs (1.1.3)
25
+ i18n (0.6.0)
26
+ multi_json (1.3.6)
27
+ pg (0.13.2)
28
+ rake (0.9.2.2)
29
+ rspec (2.10.0)
30
+ rspec-core (~> 2.10.0)
31
+ rspec-expectations (~> 2.10.0)
32
+ rspec-mocks (~> 2.10.0)
33
+ rspec-core (2.10.1)
34
+ rspec-expectations (2.10.0)
35
+ diff-lcs (~> 1.1.3)
36
+ rspec-mocks (2.10.1)
37
+ thor (0.15.2)
38
+ tzinfo (0.3.33)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ activerecord
45
+ pg_reindex!
46
+ rake
47
+ rspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Makarchev K
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ Pg Reindex
2
+ ==========
3
+
4
+ Console utility for gracefully rebuild indexes/pkeys for PostgreSQL, with minimal locking in semi-auto mode.
5
+
6
+ Install:
7
+
8
+ $ gem install pg_reindex
9
+
10
+ Using:
11
+
12
+ export PGRE_CFG=/some/path/database.yml
13
+ pgre --help
14
+
15
+ User in connection should be an owner of relations. And for rebuild pkey, should be superuser.
16
+
17
+ Tasks:
18
+
19
+ pgre dbs # Show list of databases from database.yml
20
+ pgre install ENV # Install function swap_for_pkey to database
21
+ pgre rebuild ENV (table|index)[,(table|index)] # rebuild tables,indexes,pkeys
22
+ pgre tables ENV # Show tables of database
23
+
24
+ Examples:
25
+
26
+ # Show tables with indexes, which full size more than 1Gb
27
+ pgre tables production -s 1000
28
+
29
+ # Show process without really rebuild
30
+ pgre rebuild production users,some_index1,some_index2
31
+
32
+ # Rebuild indexes
33
+ pgre rebuild production users --write
34
+
35
+ # Rebuild indexes and no ask confirmation (not recommended)
36
+ pgre rebuild production users --write --no-ask
37
+
38
+
39
+ Explanation/Warning:
40
+ --------------------
41
+
42
+ Rebuild index produces sqls:
43
+
44
+ 1. CREATE INDEX CONCURRENTLY bla2 on some_table USING btree (some_field);
45
+ 2. ANALYZE some_table;
46
+ 3. DROP INDEX bla;
47
+ 4. ALTER INDEX bla2 RENAME TO bla;
48
+
49
+ 2 can be blocked by long running query(LRQ), or autovacuum (in this case just kill autovacuum or wait LRQ).
50
+ By careful, if between 2 and (3,4) started LRQ or autovacuum, (3,4) can blocks all queries on this table. If this happens, and (3,4) not quit after < 30s, should stop (3,4) by cancel query in PostgreSQL. And execute (3,4) manually.
51
+
52
+
53
+ Rebuild pkey produces sqls:
54
+
55
+ 1. CREATE UNIQUE INDEX CONCURRENTLY some_table_pkey2 on some_table USING btree (ID);
56
+ 2. ANALYZE some_table;
57
+ 3. SELECT swap_for_pkey('public', 'some_table_pkey', 'some_table_pkey2');
58
+
59
+ Same issue with 2 and 3.
60
+
61
+
62
+
63
+ MIT-LICENCE
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'bundler'
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ require 'rspec/core/rake_task'
7
+ task :default => :spec
8
+ RSpec::Core::RakeTask.new(:spec)
data/bin/pgre ADDED
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'thor'
5
+ require 'yaml'
6
+ require File.dirname(__FILE__) + '/../lib/pg-reindex'
7
+
8
+ class PgReindexer < Thor
9
+
10
+ desc "dbs", "Show list of databases from database.yml (-n if no show sizes)"
11
+ method_option :no_size, :aliases => '-n', :type => :boolean
12
+ def dbs
13
+ list = db_list(!options[:no_size])
14
+ return unless list
15
+
16
+ render_db_list(list)
17
+ end
18
+
19
+ desc "tables ENV", "Show tables of database"
20
+ method_option :size, :aliases => '-s', :type => :string
21
+ def tables(env)
22
+ conn = get_connection(env)
23
+ relations = conn.get_struct_relations(options[:size])
24
+ render_tables(env, relations)
25
+ end
26
+
27
+ desc "rebuild ENV (table|index)[,(table|index)]", "rebuild tables,indexes,pkeys"
28
+ method_option :write, :aliases => '--write', :type => :boolean
29
+ method_option :no_ask, :aliases => '--no-ask', :type => :boolean
30
+ def rebuild(env, to_reindex = '')
31
+ if !to_reindex || to_reindex.empty?
32
+ say "blank attributes to rebuild", :red
33
+ exit 1
34
+ end
35
+
36
+ conn = get_connection(env)
37
+
38
+ rebuild = conn.filter_relations(to_reindex)
39
+
40
+ rebuild.each do |row|
41
+ do_rebuild conn, row, options[:write], !options[:no_ask]
42
+ end
43
+
44
+ if rebuild.empty?
45
+ say "nothing to rebuild", :red
46
+ end
47
+ end
48
+
49
+ desc "install ENV", "Install need function to database, like function: swap_for_pkey"
50
+ def install(env)
51
+ conn = get_connection(env)
52
+ conn.install_swap_for_pkey
53
+ say "DONE!", :green
54
+ end
55
+
56
+ desc "queries ENV", "Show current queries on database ENV"
57
+ def queries(env)
58
+ conn = get_connection(env)
59
+ q = conn.queries
60
+ r = q.map do |query|
61
+ [query['procpid'], query['age'], query['current_query']]
62
+ end
63
+
64
+ print_table r
65
+ end
66
+
67
+ desc "cancel ENV PID", "Cancel pid query"
68
+ def cancel(env, pid)
69
+ conn = get_connection(env)
70
+ conn.cancel(pid)
71
+ end
72
+
73
+ private
74
+
75
+ def get_connection(env)
76
+ conn = connection(env)
77
+ unless conn
78
+ say "Not connected to #{env}, hm!!!", :red
79
+ exit 1
80
+ end
81
+
82
+ conn
83
+ end
84
+
85
+ def connection(env)
86
+ cn = connect(env, db_config[env])
87
+ end
88
+
89
+ def connect(env, conf)
90
+ @connects ||= {}
91
+ @connects[env] ||= PgReindex.new(conf) if conf
92
+ end
93
+
94
+ # load database.yml
95
+ def db_config
96
+ @db_config ||= begin
97
+ file = ENV['PGRE_CFG'] || `pwd`.to_s.chop + "/config/database.yml"
98
+ if File.exists?(file)
99
+ YAML.load_file(file)
100
+ else
101
+ say "Specify path to database.yml with env CFG, like: CFG=/../../database.yml", :red
102
+ exit 1
103
+ end
104
+ end
105
+ end
106
+
107
+ def db_list(show_sizes = true)
108
+ cfg = db_config
109
+ return unless cfg
110
+
111
+ cfg = cfg.sort_by{|a| a[0]}
112
+ cfg.map do |env, config|
113
+ x = [env, config]
114
+ x << (connection(env).database_size(config['database']) rescue '-') if show_sizes
115
+ x
116
+ end
117
+ end
118
+
119
+ def do_rebuild(conn, row, write = true, ask = true)
120
+ #ask = false unless write
121
+
122
+ say "=> rebuild #{row['index']} (#{row['i_size_p']}), write #{write}, ask #{ask}", :yellow
123
+
124
+ sqls = conn.index_sqls(row)
125
+
126
+ if ask || !write
127
+ sqls.each do |sql|
128
+ say " #{sql}"
129
+ end
130
+ end
131
+
132
+ if ask
133
+ r = ask_until("Rebuild? (y/n)")
134
+ if r == '' || r == 'y' || r == 'Y'
135
+ else
136
+ say " ... skipped\n", :red
137
+ return
138
+ end
139
+ end
140
+
141
+ if row['index'].end_with?("_pkey")
142
+ unless conn.check_swap_for_pkey
143
+ say " ... You should install function swap_for_pkey for rebuild pkey, call: #{$0} install ENV", :red
144
+ return
145
+ end
146
+ end
147
+
148
+ started_at = Time.now
149
+
150
+ if write
151
+ sqls.each do |query|
152
+ say " => ", :bold, false
153
+ say "#{query}", nil, false
154
+ tm = Time.now
155
+
156
+ conn.exec(query)
157
+
158
+ say " (#{"%.3f" % (Time.now - tm)}s)"
159
+ end
160
+
161
+ new_size = conn.get_index_size(row['index'])['i_size_p'] rescue '-'
162
+ say "<= rebuilded #{row['index']} (#{new_size})", :yellow
163
+ say " DONE! (#{"%.3f" % (Time.now - started_at)}s)\n", :green
164
+ else
165
+ say " DRY-RUN DONE\n", :green
166
+ end
167
+ end
168
+
169
+ def render_db_list(db_list)
170
+ say "Databases list:", :green
171
+ db_list.each do |row|
172
+ say "\t#{row[0].ljust(30)} ", :yellow, false
173
+ say "#{row[1]['database']}:#{row[1]['host']}".ljust(60)
174
+ say "#{row[2]}"
175
+ end
176
+ end
177
+
178
+ def render_tables(env, relations)
179
+ off1 = 65
180
+ off2 = 15
181
+ off3 = 15
182
+
183
+ say "tables for #{env.ljust(off1 - 9)}size#{''.ljust(off2 - 4)}indexes_size", :green
184
+
185
+ relations.each do |table, indexes|
186
+ say " #{table.ljust(off1)}", :yellow, false
187
+ f = indexes[0]
188
+ say "#{f['size_p'].ljust(off2)}", :yellow, false
189
+ say "#{f['total_i_size_p'].ljust(off3)}", :yellow, true
190
+ #say "#{f['total_size_p']}", :yellow
191
+
192
+
193
+ indexes.each do |rel|
194
+ say " #{(rel['index']+" ").ljust(off1 - 2 + off2, '.')}", nil, false
195
+ say "#{rel['i_size_p']}"
196
+ end
197
+ end
198
+ end
199
+
200
+ def ask_until(str, answers = ['y', 'n', ''])
201
+ loop do
202
+ res = ask(str)
203
+ return res if answers.include?(res)
204
+ end
205
+ end
206
+
207
+ end
208
+
209
+ PgReindexer.start
data/lib/pg-reindex.rb ADDED
@@ -0,0 +1,193 @@
1
+ require 'pg'
2
+
3
+ class PgReindex
4
+
5
+ MIN_SIZE = 50 # megabyte min, total table size
6
+
7
+ def initialize(conf)
8
+ cfg = {:host => conf['host'] || '127.0.0.1', :dbname => conf['database'],
9
+ :user => conf['username'] || `whoami`.chop, :password => conf['password'] || 'password', :port => conf['port'].to_s}
10
+
11
+ @conn = PGconn.new cfg
12
+ end
13
+
14
+ def get_struct_relations(min_size = nil)
15
+ res = get_raw_relations
16
+
17
+ result = {}
18
+ res.each do |row|
19
+ next if row['total_size'].to_i < ((min_size || MIN_SIZE).to_f * 1024 * 1024).to_i
20
+ result[row['table']] ||= []
21
+ result[row['table']] << row
22
+ end
23
+
24
+ result = result.sort_by{|el| el[1][0]['total_size'].to_i}.reverse
25
+
26
+ result = result.map do |table, res|
27
+ [table, res]
28
+ end
29
+
30
+ result
31
+ end
32
+
33
+ def filter_relations(filter) # filter is a string with ,
34
+ return [] if !filter || filter.empty?
35
+
36
+ filter = filter.split(",")
37
+
38
+ get_raw_relations.select do |row|
39
+ filter.include?(row['index']) || filter.include?(row['table'])
40
+ end
41
+ end
42
+
43
+ def get_raw_relations
44
+ res = @conn.exec <<-SQL
45
+ SELECT C.relname AS "table",
46
+ i.relname "index",
47
+ i.oid "index_oid",
48
+ pg_relation_size(C.oid) AS "size",
49
+ pg_total_relation_size(C.oid) AS "total_size",
50
+ pg_size_pretty(pg_relation_size(C.oid)) AS "size_p",
51
+ pg_size_pretty(pg_total_relation_size(C.oid)) AS "total_size_p",
52
+ pg_size_pretty(pg_total_relation_size(C.oid) - pg_relation_size(C.oid)) AS "total_i_size_p",
53
+ pg_relation_size(i.oid) as "i_size",
54
+ pg_size_pretty(pg_relation_size(i.oid)) as "i_size_p"
55
+ FROM pg_class C, pg_class i, pg_index ix, pg_namespace N
56
+ WHERE nspname IN ('public') AND
57
+ C.oid = ix.indrelid and i.oid = ix.indexrelid
58
+ AND C.oid = ix.indrelid and i.oid = ix.indexrelid
59
+ AND C.relname not like 'pg_%'
60
+ AND N.oid = C.relnamespace
61
+ ORDER BY c.relname, i.relname
62
+ SQL
63
+ end
64
+
65
+ def get_index_size(relation_name)
66
+ res = @conn.exec "SELECT pg_relation_size(oid) as i_size, pg_size_pretty(pg_relation_size(oid)) as i_size_p
67
+ from pg_class WHERE relname = E'#{relation_name}'"
68
+ res[0]
69
+ rescue
70
+ {}
71
+ end
72
+
73
+ def install_swap_for_pkey
74
+ @conn.exec("create language plpgsql") rescue nil
75
+ @conn.exec(swap_for_pkey_sql)
76
+ end
77
+
78
+ def check_swap_for_pkey
79
+ res = @conn.exec <<-SQL
80
+ SELECT proname FROM pg_catalog.pg_namespace n JOIN pg_catalog.pg_proc p ON pronamespace = n.oid
81
+ WHERE nspname = 'public' and proname = 'swap_for_pkey'
82
+ SQL
83
+
84
+ res.values.size == 1
85
+ end
86
+
87
+ def swap_for_pkey_sql
88
+ <<-SQL
89
+ CREATE OR REPLACE FUNCTION swap_for_pkey(text,text,text) returns integer
90
+ AS
91
+ $$
92
+ DECLARE
93
+ cmd text;
94
+ oid1 integer;
95
+ oid2 integer;
96
+ filenode1 integer;
97
+ filenode2 integer;
98
+ relation text;
99
+ BEGIN
100
+ select oid::integer into oid1 from pg_class where relname=$2 and relnamespace = (select oid from pg_namespace where nspname=$1);
101
+ RAISE NOTICE 'PKEY OID: %',oid1;
102
+ select relfilenode::integer into filenode1 from pg_class where oid=oid1;
103
+ select oid::integer into oid2 from pg_class where relname=$3 and relnamespace = (select oid from pg_namespace where nspname=$1);
104
+ RAISE NOTICE 'PKEY OID: %',oid2;
105
+ select relfilenode::integer into filenode2 from pg_class where oid=oid2;
106
+ select (indrelid::regclass)::text into relation from pg_index where indexrelid=oid1;
107
+ RAISE NOTICE 'RELATION NAME: %',relation;
108
+ cmd:='LOCK '||relation||';';
109
+ RAISE NOTICE 'Executing :- %',cmd;
110
+ Execute cmd;
111
+ cmd:='UPDATE pg_class SET relfilenode='||filenode2|| ' WHERE oid='||oid1||';';
112
+ RAISE NOTICE 'Executing :- %',cmd;
113
+ Execute cmd;
114
+ cmd:='UPDATE pg_class SET relfilenode='||filenode1|| ' WHERE oid='||oid2||';';
115
+ RAISE NOTICE 'Executing :- %',cmd;
116
+ Execute cmd;
117
+ cmd:='DROP INDEX '||$1||'.'||$3||';';
118
+ RAISE NOTICE 'Executing :- %',cmd;
119
+ Execute cmd;
120
+ return 0;
121
+ END;
122
+ $$language plpgsql;
123
+ SQL
124
+ end
125
+
126
+ def database_size(database)
127
+ res = @conn.exec("SELECT pg_size_pretty(pg_database_size('#{database}')) as db_size")
128
+ res.first['db_size']
129
+ end
130
+
131
+ def index_sqls(index_row)
132
+ index_sql_array(index_row['table'], index_row['index_oid'], index_row['index'])
133
+ end
134
+
135
+ def index_sql_array(table, oid, name)
136
+ new_name = if name.size < 61
137
+ name + "_2"
138
+ else
139
+ name[0..-3] + "_2"
140
+ end
141
+
142
+ if name.end_with?('_pkey')
143
+ [
144
+ index_sql(table, oid, name, new_name),
145
+ "ANALYZE #{table}",
146
+ "SELECT swap_for_pkey('public', '#{name}', '#{new_name}')"
147
+ ]
148
+ else
149
+ [
150
+ index_sql(table, oid, name, new_name),
151
+ "ANALYZE #{table}",
152
+ "DROP INDEX #{name}",
153
+ "ALTER INDEX #{new_name} RENAME TO #{name}"
154
+ ]
155
+ end
156
+ end
157
+
158
+ def index_sql(table, oid, name, new_name)
159
+ str = index_def(oid).gsub(name, new_name)
160
+
161
+ pos = str.index(new_name)
162
+
163
+ before = str[0..pos-2]
164
+ after = str[pos..-1]
165
+
166
+ "#{before} CONCURRENTLY #{after}"
167
+ end
168
+
169
+ def index_def(oid)
170
+ sql = "SELECT pg_get_indexdef(#{oid}) as q"
171
+ exec(sql)[0]['q']
172
+ end
173
+
174
+ def exec(sql)
175
+ @conn.exec sql
176
+ end
177
+
178
+ def queries
179
+ @conn.exec <<-SQL
180
+ SELECT procpid, now() - query_start as age, current_query
181
+ FROM
182
+ pg_stat_activity AS a JOIN
183
+ pg_locks AS l ON a.procpid = l.pid
184
+ WHERE
185
+ virtualxid IS NOT NULL order by age desc;
186
+ SQL
187
+ end
188
+
189
+ def cancel(procpid)
190
+ @conn.exec "select pg_cancel_backend(#{procpid.to_i})"
191
+ end
192
+
193
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{pg_reindex}
5
+ s.version = "0.1.1"
6
+
7
+ s.authors = ["Makarchev Konstantin"]
8
+
9
+ s.description = %q{Console utility for gracefully rebuild indexes/pkeys for PostgreSQL, with minimal locking in semi-auto mode.}
10
+ s.summary = %q{Console utility for gracefully rebuild indexes/pkeys for PostgreSQL, with minimal locking in semi-auto mode.}
11
+
12
+ s.email = %q{kostya27@gmail.com}
13
+ s.homepage = %q{http://github.com/kostya/pg_reindex}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency 'thor'
21
+ s.add_dependency 'pg'
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "rake"
24
+ s.add_development_dependency 'activerecord'
25
+
26
+ end
@@ -0,0 +1,135 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe PgReindex do
4
+ before :each do
5
+ @pgre = PgReindex.new($cfg)
6
+ end
7
+
8
+ def row(name)
9
+ @pgre.filter_relations(name).first
10
+ end
11
+
12
+ it "get_raw_relations" do
13
+ res = @pgre.get_raw_relations
14
+ v = []
15
+ res.each{|h| v << h }
16
+
17
+ v.map{|el| el['table']}.uniq.should == ['tests']
18
+ v.map{|el| el['index']}.uniq.sort.should == ["a_b_c", "index_tests_on_a", "index_tests_on_b_and_c", "tests_pkey"]
19
+ v.map{|el| el['index_oid']}.sum.to_i.should > 0
20
+ end
21
+
22
+ describe "filter_relations" do
23
+ it "1" do
24
+ res = @pgre.filter_relations('a_b_c')
25
+ res.size.should == 1
26
+ res[0]['index'].should == 'a_b_c'
27
+ end
28
+
29
+ it "2" do
30
+ res = @pgre.filter_relations('a_b_c,index_tests_on_a,tests_pkey,xxx')
31
+ res.size.should == 3
32
+ res.map{|el| el['index']}.sort.should == ["a_b_c", "index_tests_on_a", "tests_pkey"]
33
+ end
34
+
35
+ it "3" do
36
+ res = @pgre.filter_relations('tests,xxx')
37
+ res.size.should == 4
38
+ res.map{|el| el['index']}.sort.should == ["a_b_c", "index_tests_on_a", "index_tests_on_b_and_c", "tests_pkey"]
39
+ end
40
+
41
+ it "4" do
42
+ res = @pgre.filter_relations('xxx')
43
+ res.size.should == 0
44
+ end
45
+
46
+ it "5" do
47
+ res = @pgre.filter_relations('tests,a_b_c')
48
+ res.size.should == 4
49
+ res.map{|el| el['index']}.sort.should == ["a_b_c", "index_tests_on_a", "index_tests_on_b_and_c", "tests_pkey"]
50
+ end
51
+ end
52
+
53
+ it "database_size" do
54
+ s = @pgre.database_size('pgre_test')
55
+ s.to_i.should > 1
56
+ end
57
+
58
+ describe "index sqls" do
59
+ it "index sqls for a" do
60
+ @pgre.index_sqls(row('index_tests_on_a')).should == ["CREATE INDEX CONCURRENTLY index_tests_on_a_2 ON tests USING btree (a)",
61
+ "ANALYZE tests", "DROP INDEX index_tests_on_a", "ALTER INDEX index_tests_on_a_2 RENAME TO index_tests_on_a"]
62
+ end
63
+
64
+ it "index sqls for b,c" do
65
+ @pgre.index_sqls(row('index_tests_on_b_and_c')).should == ["CREATE INDEX CONCURRENTLY index_tests_on_b_and_c_2 ON tests USING btree (b, c)",
66
+ "ANALYZE tests", "DROP INDEX index_tests_on_b_and_c", "ALTER INDEX index_tests_on_b_and_c_2 RENAME TO index_tests_on_b_and_c"]
67
+ end
68
+
69
+ it "index sqls for a,b,c" do
70
+ @pgre.index_sqls(row('a_b_c')).should == ["CREATE UNIQUE INDEX CONCURRENTLY a_b_c_2 ON tests USING btree (a, b, c) WHERE ((a > 0) AND (b < 0))",
71
+ "ANALYZE tests", "DROP INDEX a_b_c", "ALTER INDEX a_b_c_2 RENAME TO a_b_c"]
72
+ end
73
+
74
+ it "index sqls for pkey" do
75
+ @pgre.index_sqls(row('tests_pkey')).should == ["CREATE UNIQUE INDEX CONCURRENTLY tests_pkey_2 ON tests USING btree (id)",
76
+ "ANALYZE tests", "SELECT swap_for_pkey('public', 'tests_pkey', 'tests_pkey_2')"]
77
+ end
78
+ end
79
+
80
+ it "index def" do
81
+ r = row('a_b_c')
82
+ @pgre.index_def(r['index_oid']).should == "CREATE UNIQUE INDEX a_b_c ON tests USING btree (a, b, c) WHERE ((a > 0) AND (b < 0))"
83
+ end
84
+
85
+ it "rebuilds index" do
86
+ r = row('a_b_c')
87
+ r['index_oid'].to_i.should > 0
88
+ def1 = @pgre.index_def(r['index_oid'])
89
+
90
+ sqls = @pgre.index_sqls(r)
91
+ sqls.each do |sql|
92
+ @pgre.exec sql
93
+ end
94
+
95
+ r2 = row('a_b_c')
96
+ r2['index_oid'].to_i.should > 0
97
+
98
+ r2['index_oid'].should_not == r['index_oid']
99
+ r2['index'].should == r['index']
100
+
101
+ def2 = @pgre.index_def(r2['index_oid'])
102
+ def1.should == def2
103
+
104
+ # index should be
105
+ res = @pgre.filter_relations('tests')
106
+ res.size.should == 4
107
+ res.map{|el| el['index']}.sort.should == ["a_b_c", "index_tests_on_a", "index_tests_on_b_and_c", "tests_pkey"]
108
+ end
109
+
110
+ it "rebuilds pkey" do
111
+ # install swap_for_pkey
112
+ @pgre.install_swap_for_pkey
113
+
114
+ r = row('tests_pkey')
115
+ r['index_oid'].to_i.should > 0
116
+ def1 = @pgre.index_def(r['index_oid'])
117
+
118
+ sqls = @pgre.index_sqls(r)
119
+ sqls.each do |sql|
120
+ @pgre.exec sql
121
+ end
122
+
123
+ r2 = row('tests_pkey')
124
+ r2['index_oid'].to_i.should > 0
125
+
126
+ r2['index_oid'].should == r['index_oid']
127
+ def1.should == @pgre.index_def(r2['index_oid'])
128
+
129
+ # index should be
130
+ res = @pgre.filter_relations('tests')
131
+ res.size.should == 4
132
+ res.map{|el| el['index']}.sort.should == ["a_b_c", "index_tests_on_a", "index_tests_on_b_and_c", "tests_pkey"]
133
+ end
134
+
135
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require "bundler"
3
+ Bundler.setup
4
+ ENV['RAILS_ENV'] ||= 'test'
5
+
6
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
7
+ require 'pg-reindex'
8
+
9
+ require 'active_record'
10
+
11
+ require File.dirname(__FILE__) + '/spec_support.rb'
@@ -0,0 +1,25 @@
1
+ $cfg = {'adapter' => 'postgresql', 'database' => 'pgre_test', 'encoding' => 'utf8', 'username' => 'kostya', 'password' => 'password'}
2
+ ActiveRecord::Base.establish_connection $cfg
3
+
4
+ class Test < ActiveRecord::Base
5
+ self.table_name = 'tests'
6
+ end
7
+
8
+ def pg_create_schema
9
+ ActiveRecord::Migration.create_table :tests do |t|
10
+ t.integer :a
11
+ t.integer :b
12
+ t.integer :c
13
+ end
14
+
15
+ ActiveRecord::Migration.add_index :tests, :a
16
+ ActiveRecord::Migration.add_index :tests, [:b, :c]
17
+ ActiveRecord::Migration.execute "create unique index a_b_c on tests using btree(a,b,c) where a > 0 and b < 0"
18
+ end
19
+
20
+ def pg_drop_data
21
+ ActiveRecord::Migration.drop_table :tests
22
+ end
23
+
24
+ pg_drop_data rescue nil
25
+ pg_create_schema
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pg_reindex
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 1
10
+ version: 0.1.1
11
+ platform: ruby
12
+ authors:
13
+ - Makarchev Konstantin
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-06-07 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: thor
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: pg
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: rspec
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ type: :development
61
+ version_requirements: *id003
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ prerelease: false
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ type: :development
75
+ version_requirements: *id004
76
+ - !ruby/object:Gem::Dependency
77
+ name: activerecord
78
+ prerelease: false
79
+ requirement: &id005 !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ type: :development
89
+ version_requirements: *id005
90
+ description: Console utility for gracefully rebuild indexes/pkeys for PostgreSQL, with minimal locking in semi-auto mode.
91
+ email: kostya27@gmail.com
92
+ executables:
93
+ - pgre
94
+ extensions: []
95
+
96
+ extra_rdoc_files: []
97
+
98
+ files:
99
+ - .gitignore
100
+ - Gemfile
101
+ - Gemfile.lock
102
+ - MIT-LICENSE
103
+ - README.md
104
+ - Rakefile
105
+ - bin/pgre
106
+ - lib/pg-reindex.rb
107
+ - pg_reindex.gemspec
108
+ - spec/reindex_spec.rb
109
+ - spec/spec_helper.rb
110
+ - spec/spec_support.rb
111
+ homepage: http://github.com/kostya/pg_reindex
112
+ licenses: []
113
+
114
+ post_install_message:
115
+ rdoc_options: []
116
+
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ hash: 3
125
+ segments:
126
+ - 0
127
+ version: "0"
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ hash: 3
134
+ segments:
135
+ - 0
136
+ version: "0"
137
+ requirements: []
138
+
139
+ rubyforge_project:
140
+ rubygems_version: 1.8.24
141
+ signing_key:
142
+ specification_version: 3
143
+ summary: Console utility for gracefully rebuild indexes/pkeys for PostgreSQL, with minimal locking in semi-auto mode.
144
+ test_files: []
145
+