mschuerig-index_lifter 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2009-04-02
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/Manifest.txt ADDED
@@ -0,0 +1,7 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.rdoc
4
+ Rakefile
5
+ lib/index_lifter.rb
6
+ test/test_helper.rb
7
+ test/test_index_lifter.rb
data/README.rdoc ADDED
@@ -0,0 +1,75 @@
1
+ = index_lifter
2
+
3
+ http://github.com/mschuerig/index_lifter
4
+
5
+ == DESCRIPTION:
6
+
7
+ ActiveRecord utility class that disables database indexes during a block.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Speeds up large database imports and large-scale data generation
12
+ * Only use it in "maintenance mode", i.e., when there are no other
13
+ connections to the database.
14
+ * If your application depends on unique indexes for working properly
15
+ even during data generation, make sure they are not lifted.
16
+
17
+ == SYNOPSIS:
18
+
19
+ desc "Populate the database with sample data"
20
+ task :populate => :environment do
21
+
22
+ retained_indexes = [
23
+ 'index_people_on_lastname_and_firstname',
24
+ { :table => :movies, :columns => :title }
25
+ { :table => 'people', :columns => ['lastname', :firstname] }
26
+ ]
27
+
28
+ ActiveRecord::Base.transaction do
29
+ IndexLifter.without_indexes(
30
+ :movies, # Only consider indexes on these tables;
31
+ :people, # all tables by default.
32
+ :except => retained_indexes, # Don't lift these indexes
33
+ :except_unique => true # Don't lift unique indexes; default: false.
34
+ ) do
35
+ ActiveRecord::Base.silence do
36
+
37
+ # import or generate large amounts of data here
38
+
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ == REQUIREMENTS:
45
+
46
+ * ActiveRecord
47
+
48
+ == INSTALL:
49
+
50
+ * sudo gem install index_lifter
51
+
52
+ == LICENSE:
53
+
54
+ (The MIT License)
55
+
56
+ Copyright (c) 2009 Michael Schuerig
57
+
58
+ Permission is hereby granted, free of charge, to any person obtaining
59
+ a copy of this software and associated documentation files (the
60
+ 'Software'), to deal in the Software without restriction, including
61
+ without limitation the rights to use, copy, modify, merge, publish,
62
+ distribute, sublicense, and/or sell copies of the Software, and to
63
+ permit persons to whom the Software is furnished to do so, subject to
64
+ the following conditions:
65
+
66
+ The above copyright notice and this permission notice shall be
67
+ included in all copies or substantial portions of the Software.
68
+
69
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
70
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
71
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
72
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
73
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
74
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
75
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ %w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
2
+ require File.dirname(__FILE__) + '/lib/index_lifter'
3
+
4
+ # Generate all the Rake tasks
5
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
+ $hoe = Hoe.new('index_lifter', IndexLifter::VERSION) do |p|
7
+ p.developer('Michael Schuerig', 'michael@schuerig.de')
8
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
9
+ p.extra_deps = [
10
+ ['activesupport','>= 2.0.2'],
11
+ ]
12
+ p.extra_dev_deps = [
13
+ ['newgem', ">= #{::Newgem::VERSION}"]
14
+ ]
15
+
16
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
17
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
18
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
19
+ p.rsync_args = '-av --delete --ignore-errors'
20
+ end
21
+
22
+ require 'newgem/tasks' # load /tasks/*.rake
23
+ Dir['tasks/**/*.rake'].each { |t| load t }
@@ -0,0 +1,66 @@
1
+
2
+ class IndexLifter
3
+ VERSION = '0.0.1'
4
+ SYSTEM_TABLES = ['schema_migrations'].freeze
5
+ attr_reader :tables, :indexes
6
+
7
+ def self.without_indexes(*tables, &block)
8
+ IndexLifter.new(*tables).without_indexes(&block)
9
+ end
10
+
11
+ def initialize(*tables)
12
+ @options = tables.extract_options!
13
+ @connection = @options[:connection] || ActiveRecord::Base.connection
14
+ @tables = (tables.empty? ? @connection.tables : tables) - SYSTEM_TABLES
15
+ @indexes = index_definitions
16
+ @tables.freeze
17
+ @indexes.freeze
18
+ end
19
+
20
+ def without_indexes(&block)
21
+ lift_indexes(indexes)
22
+ yield
23
+ ensure
24
+ reinstate_indexes(indexes)
25
+ end
26
+
27
+ private
28
+
29
+ def index_definitions
30
+ indexes = @tables.inject([]) { |defs, table|
31
+ defs += @connection.indexes(table)
32
+ }
33
+ if @options[:except_unique]
34
+ indexes.delete_if(&:unique)
35
+ end
36
+ exceptions = [@options[:except]].flatten
37
+
38
+ exception_names = exceptions.map { |exc|
39
+ (exc.kind_of?(::Hash) ? exc[:name] : exc)
40
+ }.compact.map(&:to_s)
41
+ indexes.delete_if { |idx| exception_names.include?(idx.name) }
42
+
43
+ exception_hashes = exceptions.select { |exc| exc.kind_of?(::Hash) }.map { |exc|
44
+ { :table => exc[:table].to_s,
45
+ :columns => Array(exc[:column] || exc[:columns]).map(&:to_s)
46
+ }}
47
+ indexes.delete_if { |idx| exception_hashes.any? { |exc|
48
+ exc[:table] == idx.table &&
49
+ exc[:columns] == idx.columns
50
+ }}
51
+
52
+ indexes
53
+ end
54
+
55
+ def lift_indexes(indexes)
56
+ indexes.each do |idx|
57
+ @connection.remove_index(idx.table, :name => idx.name)
58
+ end
59
+ end
60
+
61
+ def reinstate_indexes(indexes)
62
+ indexes.each do |idx|
63
+ @connection.add_index(idx.table, idx.columns, :unique => idx.unique)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,3 @@
1
+ require 'stringio'
2
+ require 'test/unit'
3
+ require File.dirname(__FILE__) + '/../lib/index_lifter'
@@ -0,0 +1,102 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ require 'active_support'
4
+
5
+ class TestIndexLifter < Test::Unit::TestCase
6
+
7
+ class TestConnection
8
+ #<struct ActiveRecord::ConnectionAdapters::IndexDefinition
9
+ # table="movies",
10
+ # name="index_movies_on_title",
11
+ # unique=false,
12
+ # columns=["title"]>
13
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns)
14
+ def to_hash
15
+ { :table => table, :name => name, :unique => unique, :columns => columns }
16
+ end
17
+ end
18
+
19
+ def initialize(*defs)
20
+ @indexes = defs.inject({}) do |memo, idef|
21
+ memo[idef[:table].to_s] ||= []
22
+ memo[idef[:table].to_s] << IndexDefinition.new(
23
+ idef[:table].to_s,
24
+ idef[:name].to_s,
25
+ idef[:unique],
26
+ Array(idef[:columns]).map { |c| c.to_s }
27
+ )
28
+ memo
29
+ end
30
+ end
31
+ def tables
32
+ @indexes.keys.sort
33
+ end
34
+ def indexes(table)
35
+ @indexes[table.to_s]
36
+ end
37
+ end
38
+
39
+ def setup
40
+ @index_people_on_lastname_firstname_unique = {
41
+ :name => 'index_people_on_lastname_firstname',
42
+ :table => 'people', :columns => ['lastname', 'firstname'], :unique => true
43
+ }
44
+ @index_movies_on_title = {
45
+ :name => 'index_movies_on_title',
46
+ :table => 'movies', :columns => ['title'], :unique => false
47
+ }
48
+ @connection = TestConnection.new(
49
+ @index_movies_on_title,
50
+ @index_people_on_lastname_firstname_unique
51
+ )
52
+ end
53
+
54
+ def test_find_indexes
55
+ lifter = IndexLifter.new(:connection => @connection)
56
+ assert_equal [@index_movies_on_title, @index_people_on_lastname_firstname_unique],
57
+ lifter.indexes.map(&:to_hash)
58
+ end
59
+
60
+ def test_exclude_unique_indexes
61
+ lifter = IndexLifter.new(:connection => @connection,
62
+ :except_unique => true)
63
+ assert_equal [@index_movies_on_title],
64
+ lifter.indexes.map(&:to_hash)
65
+ end
66
+
67
+ def test_exclude_index_by_name_as_string
68
+ lifter = IndexLifter.new(:connection => @connection,
69
+ :except => 'index_people_on_lastname_firstname')
70
+ assert_equal [@index_movies_on_title],
71
+ lifter.indexes.map(&:to_hash)
72
+ end
73
+
74
+ def test_exclude_index_by_hashed_name
75
+ lifter = IndexLifter.new(:connection => @connection,
76
+ :except => { :name => 'index_people_on_lastname_firstname' })
77
+ assert_equal [@index_movies_on_title],
78
+ lifter.indexes.map(&:to_hash)
79
+ end
80
+
81
+ def test_exclude_index_by_name_array
82
+ lifter = IndexLifter.new(:connection => @connection,
83
+ :except => ['index_people_on_lastname_firstname'])
84
+ assert_equal [@index_movies_on_title],
85
+ lifter.indexes.map(&:to_hash)
86
+ end
87
+
88
+ def test_exclude_index_by_table_and_column
89
+ lifter = IndexLifter.new(:connection => @connection,
90
+ :except => { :table => :movies, :column => :title})
91
+ assert_equal [@index_people_on_lastname_firstname_unique],
92
+ lifter.indexes.map(&:to_hash)
93
+ end
94
+
95
+ def test_exclude_index_by_table_and_columns
96
+ lifter = IndexLifter.new(:connection => @connection,
97
+ :except => { :table => :people, :columns => [:lastname, :firstname]})
98
+ assert_equal [@index_movies_on_title],
99
+ lifter.indexes.map(&:to_hash)
100
+ end
101
+
102
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mschuerig-index_lifter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Michael Schuerig
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-02 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.0.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: newgem
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.3.0
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: hoe
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.8.0
44
+ version:
45
+ description: ActiveRecord utility class that disables database indexes during a block.
46
+ email:
47
+ - michael@schuerig.de
48
+ executables: []
49
+
50
+ extensions: []
51
+
52
+ extra_rdoc_files:
53
+ - History.txt
54
+ - Manifest.txt
55
+ - README.rdoc
56
+ files:
57
+ - History.txt
58
+ - Manifest.txt
59
+ - README.rdoc
60
+ - Rakefile
61
+ - lib/index_lifter.rb
62
+ - test/test_helper.rb
63
+ - test/test_index_lifter.rb
64
+ has_rdoc: true
65
+ homepage: http://github.com/mschuerig/index_lifter
66
+ post_install_message:
67
+ rdoc_options:
68
+ - --main
69
+ - README.rdoc
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ requirements: []
85
+
86
+ rubyforge_project: index_lifter
87
+ rubygems_version: 1.2.0
88
+ signing_key:
89
+ specification_version: 2
90
+ summary: ActiveRecord utility class that disables database indexes during a block.
91
+ test_files:
92
+ - test/test_helper.rb
93
+ - test/test_index_lifter.rb