db_leftovers 0.9.2 → 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/.document +0 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +8 -0
- data/LICENSE.txt +0 -0
- data/README.html +16 -9
- data/README.md +13 -8
- data/Rakefile +0 -0
- data/TODO +0 -7
- data/VERSION +1 -1
- data/db_leftovers.gemspec +12 -4
- data/lib/db_leftovers.rb +3 -1
- data/lib/db_leftovers/constraint.rb +0 -0
- data/lib/db_leftovers/definition.rb +5 -2
- data/lib/db_leftovers/dsl.rb +13 -1
- data/lib/db_leftovers/foreign_key.rb +0 -0
- data/lib/db_leftovers/generic_database_interface.rb +67 -0
- data/lib/db_leftovers/index.rb +0 -0
- data/lib/db_leftovers/mysql_database_interface.rb +83 -0
- data/lib/db_leftovers/{database_interface.rb → postgres_database_interface.rb} +15 -58
- data/lib/db_leftovers/table_dsl.rb +0 -0
- data/lib/tasks/leftovers.rake +0 -0
- data/spec/config/database.yml.sample +27 -0
- data/spec/db_leftovers_spec.rb +112 -178
- data/spec/mysql_spec.rb +32 -0
- data/spec/postgres_spec.rb +103 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/mock_database_interface.rb +59 -0
- data/spec/support/shared_db_tests.rb +152 -0
- data/spec/support/sql_matcher.rb +11 -0
- metadata +12 -4
data/spec/mysql_spec.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rails'
|
2
|
+
require 'active_record'
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
4
|
+
|
5
|
+
def drop_all_mysql_tables(conn, database_name)
|
6
|
+
table_names = conn.select_values("SELECT table_name FROM information_schema.tables WHERE table_schema = '#{database_name}'")
|
7
|
+
# puts "MySQL drop_all_tables #{table_names.join(',')}"
|
8
|
+
conn.execute("SET FOREIGN_KEY_CHECKS = 0")
|
9
|
+
table_names.each do |t|
|
10
|
+
conn.execute("DROP TABLE IF EXISTS #{t}") # In MySQL, CASCADE does nothing here.
|
11
|
+
end
|
12
|
+
conn.execute("SET FOREIGN_KEY_CHECKS = 1")
|
13
|
+
end
|
14
|
+
|
15
|
+
describe DBLeftovers::MysqlDatabaseInterface do
|
16
|
+
|
17
|
+
if not test_database_yml('mysql')
|
18
|
+
it "WARN: Skipping MySQL tests because no database found. Use spec/config/database.yml to configure one."
|
19
|
+
else
|
20
|
+
before do
|
21
|
+
y = test_database_yml('mysql')
|
22
|
+
@conn = test_db_connection(nil, y)
|
23
|
+
@db = DBLeftovers::MysqlDatabaseInterface.new(@conn)
|
24
|
+
drop_all_mysql_tables(@conn, y['database'])
|
25
|
+
fresh_tables(@conn, y['database'])
|
26
|
+
end
|
27
|
+
|
28
|
+
it_behaves_like "DatabaseInterface"
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'rails'
|
2
|
+
require 'active_record'
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
4
|
+
|
5
|
+
def drop_all_postgres_tables(conn, database_name)
|
6
|
+
table_names = conn.select_values("SELECT table_name FROM information_schema.tables WHERE table_catalog = '#{database_name}' AND table_schema NOT IN ('pg_catalog', 'information_schema')")
|
7
|
+
# puts "Postgres drop_all_tables #{table_names.join(',')}"
|
8
|
+
if table_names.size > 0
|
9
|
+
conn.execute("DROP TABLE #{table_names.join(",")} CASCADE")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe DBLeftovers::PostgresDatabaseInterface do
|
14
|
+
|
15
|
+
if not test_database_yml('postgres')
|
16
|
+
it "WARN: Skipping Postgres tests because no database found. Use spec/config/database.yml to configure one."
|
17
|
+
else
|
18
|
+
before do
|
19
|
+
y = test_database_yml('postgres')
|
20
|
+
@conn = test_db_connection(nil, y)
|
21
|
+
@db = DBLeftovers::PostgresDatabaseInterface.new(@conn)
|
22
|
+
drop_all_postgres_tables(@conn, y['database'])
|
23
|
+
fresh_tables(@conn, y['database'])
|
24
|
+
end
|
25
|
+
|
26
|
+
it_behaves_like "DatabaseInterface"
|
27
|
+
|
28
|
+
it "should create indexes with a WHERE clause" do
|
29
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
30
|
+
index :books, :publisher_id, :where => 'publication_year IS NOT NULL'
|
31
|
+
end
|
32
|
+
@db.lookup_all_indexes.size.should == 1
|
33
|
+
@db.lookup_all_indexes.keys.sort.should == ['index_books_on_publisher_id']
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should not create indexes with a WHERE clause when they already exist" do
|
37
|
+
starts_with(@db, [
|
38
|
+
DBLeftovers::Index.new(:books, :shelf_id),
|
39
|
+
DBLeftovers::Index.new(:books, :publisher_id, :where => 'publication_year IS NOT NULL')
|
40
|
+
])
|
41
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
42
|
+
index :books, :shelf_id
|
43
|
+
index :books, :publisher_id, :where => 'publication_year IS NOT NULL'
|
44
|
+
end
|
45
|
+
@db.lookup_all_indexes.size.should == 2
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
it "should redefine indexes when they have a new WHERE clause" do
|
51
|
+
starts_with(@db, [
|
52
|
+
DBLeftovers::Index.new(:books, :shelf_id),
|
53
|
+
DBLeftovers::Index.new(:books, :publisher_id, :where => 'publication_year IS NOT NULL'),
|
54
|
+
DBLeftovers::Index.new(:books, :isbn)
|
55
|
+
])
|
56
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
57
|
+
index :books, :shelf_id, :where => 'name IS NOT NULL'
|
58
|
+
index :books, :publisher_id, :where => 'publication_year > 1900'
|
59
|
+
index :books, :isbn
|
60
|
+
end
|
61
|
+
@db.lookup_all_indexes.size.should == 3
|
62
|
+
@db.lookup_all_indexes['index_books_on_shelf_id'].where_clause.should == 'name IS NOT NULL'
|
63
|
+
@db.lookup_all_indexes['index_books_on_publisher_id'].where_clause.should == 'publication_year > 1900'
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
it "should create CHECK constraints on an empty database" do
|
69
|
+
starts_with(@db, [], [], [])
|
70
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
71
|
+
check :books, :books_have_positive_pages, 'pages_count > 0'
|
72
|
+
end
|
73
|
+
@db.lookup_all_constraints.size.should == 1
|
74
|
+
@db.lookup_all_constraints['books_have_positive_pages'].check.should == 'pages_count > 0'
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
it "should remove obsolete CHECK constraints" do
|
80
|
+
starts_with(@db, [], [], [
|
81
|
+
DBLeftovers::Constraint.new(:books_have_positive_pages, :books, 'pages_count > 0')
|
82
|
+
])
|
83
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
84
|
+
end
|
85
|
+
@db.lookup_all_constraints.size.should == 0
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
it "should drop and re-create changed CHECK constraints" do
|
91
|
+
starts_with(@db, [], [], [
|
92
|
+
DBLeftovers::Constraint.new(:books_have_positive_pages, :books, 'pages_count > 0')
|
93
|
+
])
|
94
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
95
|
+
check :books, :books_have_positive_pages, 'pages_count > 12'
|
96
|
+
end
|
97
|
+
@db.lookup_all_constraints.size.should == 1
|
98
|
+
@db.lookup_all_constraints['books_have_positive_pages'].check.should == 'pages_count > 12'
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
data/spec/spec_helper.rb
CHANGED
@@ -11,3 +11,24 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
|
11
11
|
RSpec.configure do |config|
|
12
12
|
|
13
13
|
end
|
14
|
+
|
15
|
+
|
16
|
+
def test_db_connection(adapter, conf)
|
17
|
+
# DBI.connect(dbi_uri(adapter, conf), conf['username'], conf['password'])
|
18
|
+
# RDBI.connect(adapter, conf)
|
19
|
+
ActiveRecord::Base.establish_connection(conf)
|
20
|
+
ActiveRecord::Base.connection
|
21
|
+
end
|
22
|
+
|
23
|
+
# def dbi_uri(adapter, conf)
|
24
|
+
# "DBI:#{adapter}:#{conf.select{|k,v| k != 'username' and k != 'password'}.map{|k,v| "#{k}=#{v}"}.join(";")}"
|
25
|
+
# end
|
26
|
+
|
27
|
+
def test_database_yml(database)
|
28
|
+
y = YAML.load(File.open(File.join(File.expand_path(File.dirname(__FILE__)), 'config', 'database.yml')))
|
29
|
+
y[database]
|
30
|
+
rescue Errno::ENOENT
|
31
|
+
return nil
|
32
|
+
end
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class DBLeftovers::MockDatabaseInterface < DBLeftovers::GenericDatabaseInterface
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
@sqls = []
|
5
|
+
end
|
6
|
+
|
7
|
+
def sqls
|
8
|
+
@sqls
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute_sql(sql)
|
12
|
+
@sqls << normal_whitespace(sql)
|
13
|
+
end
|
14
|
+
|
15
|
+
alias :old_execute_add_index :execute_add_index
|
16
|
+
def execute_add_index(idx)
|
17
|
+
old_execute_add_index(idx)
|
18
|
+
@indexes[idx.index_name] = idx
|
19
|
+
end
|
20
|
+
|
21
|
+
alias :old_execute_add_constraint :execute_add_constraint
|
22
|
+
def execute_add_constraint(chk)
|
23
|
+
old_execute_add_constraint(chk)
|
24
|
+
@constraints[chk.constraint_name] = chk
|
25
|
+
end
|
26
|
+
|
27
|
+
def saw_sql(sql)
|
28
|
+
# puts sqls.join("\n\n\n")
|
29
|
+
# Don't fail if only the whitespace is different:
|
30
|
+
sqls.include?(normal_whitespace(sql))
|
31
|
+
end
|
32
|
+
|
33
|
+
def starts_with(indexes=[], foreign_keys=[], constraints=[])
|
34
|
+
# Convert symbols to strings:
|
35
|
+
@indexes = Hash[indexes.map{|idx| [idx.index_name, idx]}]
|
36
|
+
@foreign_keys = Hash[foreign_keys.map{|fk| [fk.constraint_name, fk]}]
|
37
|
+
@constraints = Hash[constraints.map{|chk| [chk.constraint_name, chk]}]
|
38
|
+
end
|
39
|
+
|
40
|
+
def lookup_all_indexes
|
41
|
+
@indexes
|
42
|
+
end
|
43
|
+
|
44
|
+
def lookup_all_foreign_keys
|
45
|
+
@foreign_keys
|
46
|
+
end
|
47
|
+
|
48
|
+
def lookup_all_constraints
|
49
|
+
@constraints
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def normal_whitespace(sql)
|
55
|
+
sql.gsub(/\s/m, ' ').gsub(/ +/, ' ').strip
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
@@ -0,0 +1,152 @@
|
|
1
|
+
|
2
|
+
=begin
|
3
|
+
def drop_all_tables(conn, database_name)
|
4
|
+
table_names = conn.select_values("SELECT table_name FROM information_schema.tables WHERE table_catalog = '#{database_name}' AND table_schema NOT IN ('pg_catalog', 'information_schema')")
|
5
|
+
table_names.each do |t|
|
6
|
+
conn.execute("DROP TABLE IF EXISTS #{t} CASCADE")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
=end
|
10
|
+
|
11
|
+
def fresh_tables(conn, database_name)
|
12
|
+
conn.execute <<-EOQ
|
13
|
+
CREATE TABLE publishers (
|
14
|
+
id integer PRIMARY KEY,
|
15
|
+
name varchar(255)
|
16
|
+
)
|
17
|
+
EOQ
|
18
|
+
conn.execute <<-EOQ
|
19
|
+
CREATE TABLE authors (
|
20
|
+
id integer PRIMARY KEY,
|
21
|
+
name varchar(255)
|
22
|
+
)
|
23
|
+
EOQ
|
24
|
+
conn.execute <<-EOQ
|
25
|
+
CREATE TABLE shelves (
|
26
|
+
id integer PRIMARY KEY,
|
27
|
+
name varchar(255)
|
28
|
+
)
|
29
|
+
EOQ
|
30
|
+
conn.execute <<-EOQ
|
31
|
+
CREATE TABLE books (
|
32
|
+
id integer PRIMARY KEY,
|
33
|
+
name varchar(255),
|
34
|
+
author_id integer,
|
35
|
+
coauthor_id integer,
|
36
|
+
publisher_id integer,
|
37
|
+
isbn varchar(255),
|
38
|
+
publication_year integer,
|
39
|
+
shelf_id integer,
|
40
|
+
pages_count integer
|
41
|
+
)
|
42
|
+
EOQ
|
43
|
+
end
|
44
|
+
|
45
|
+
def starts_with(db, indexes=[], foreign_keys=[], constraints=[])
|
46
|
+
indexes.each { |idx| db.execute_add_index(idx) }
|
47
|
+
foreign_keys.each { |fk| db.execute_add_foreign_key(fk) }
|
48
|
+
constraints.each { |chk| db.execute_add_constraint(chk) }
|
49
|
+
end
|
50
|
+
|
51
|
+
shared_examples_for "DatabaseInterface" do
|
52
|
+
|
53
|
+
it "should create indexes on a fresh database" do
|
54
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
55
|
+
index :books, :shelf_id
|
56
|
+
index :books, :isbn, :unique => true
|
57
|
+
# Not supported by MYSQL:
|
58
|
+
# index :books, :publisher_id, :where => 'publication_year IS NOT NULL'
|
59
|
+
end
|
60
|
+
@db.lookup_all_indexes.size.should == 2
|
61
|
+
@db.lookup_all_indexes.keys.sort.should == ['index_books_on_isbn', 'index_books_on_shelf_id']
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should create foreign keys on a fresh database" do
|
65
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
66
|
+
foreign_key :books, :shelf_id, :shelves
|
67
|
+
foreign_key :books, :publisher_id, :publishers, :id, :on_delete => :set_null
|
68
|
+
foreign_key :books, :author_id, :authors, :id, :on_delete => :cascade
|
69
|
+
end
|
70
|
+
@db.lookup_all_foreign_keys.size.should == 3
|
71
|
+
@db.lookup_all_foreign_keys.keys.sort.should == ['fk_books_author_id', 'fk_books_publisher_id', 'fk_books_shelf_id']
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should not create indexes when they already exist" do
|
75
|
+
starts_with(@db, [
|
76
|
+
DBLeftovers::Index.new(:books, :shelf_id),
|
77
|
+
DBLeftovers::Index.new(:books, :publisher_id, :unique => true)
|
78
|
+
])
|
79
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
80
|
+
index :books, :shelf_id
|
81
|
+
index :books, :publisher_id, :unique => true
|
82
|
+
end
|
83
|
+
@db.lookup_all_indexes.size.should == 2
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
it "should create indexes when they have been redefined" do
|
89
|
+
starts_with(@db, [
|
90
|
+
DBLeftovers::Index.new(:books, :shelf_id),
|
91
|
+
# DBLeftovers::Index.new(:books, :publisher_id, :where => 'published'),
|
92
|
+
DBLeftovers::Index.new(:books, :isbn, :unique => true)
|
93
|
+
])
|
94
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
95
|
+
index :books, :shelf_id, :unique => true
|
96
|
+
# index :books, :publisher_id
|
97
|
+
index :books, :isbn
|
98
|
+
end
|
99
|
+
@db.lookup_all_indexes.size.should == 2
|
100
|
+
@db.lookup_all_indexes['index_books_on_shelf_id'].unique.should == true
|
101
|
+
@db.lookup_all_indexes['index_books_on_isbn'].unique.should == false
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
it "should drop indexes when they are removed from the definition" do
|
106
|
+
starts_with(@db, [
|
107
|
+
DBLeftovers::Index.new(:books, :shelf_id),
|
108
|
+
DBLeftovers::Index.new(:books, :isbn, :unique => true)
|
109
|
+
])
|
110
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
111
|
+
index :books, :shelf_id
|
112
|
+
end
|
113
|
+
@db.lookup_all_indexes.size.should == 1
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
|
118
|
+
it "should drop foreign keys when they are removed from the definition" do
|
119
|
+
starts_with(@db, [], [
|
120
|
+
DBLeftovers::ForeignKey.new('fk_books_shelf_id', 'books', 'shelf_id', 'shelves', 'id'),
|
121
|
+
DBLeftovers::ForeignKey.new('fk_books_author_id', 'books', 'author_id', 'authors', 'id')
|
122
|
+
])
|
123
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
124
|
+
foreign_key :books, :shelf_id, :shelves
|
125
|
+
end
|
126
|
+
@db.lookup_all_foreign_keys.size.should == 1
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
it "should support creating multi-column indexes" do
|
131
|
+
starts_with(@db)
|
132
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
133
|
+
index :books, [:publication_year, :name]
|
134
|
+
end
|
135
|
+
@db.lookup_all_indexes.size.should == 1
|
136
|
+
puts @db.lookup_all_indexes.keys
|
137
|
+
@db.lookup_all_indexes.should have_key('index_books_on_publication_year_and_name')
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
|
142
|
+
it "should support dropping multi-column indexes" do
|
143
|
+
starts_with(@db, [
|
144
|
+
DBLeftovers::Index.new(:books, [:publication_year, :name])
|
145
|
+
])
|
146
|
+
DBLeftovers::Definition.define :db_interface => @db do
|
147
|
+
end
|
148
|
+
@db.lookup_all_indexes.size.should == 0
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: db_leftovers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-10-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -114,15 +114,23 @@ files:
|
|
114
114
|
- db_leftovers.gemspec
|
115
115
|
- lib/db_leftovers.rb
|
116
116
|
- lib/db_leftovers/constraint.rb
|
117
|
-
- lib/db_leftovers/database_interface.rb
|
118
117
|
- lib/db_leftovers/definition.rb
|
119
118
|
- lib/db_leftovers/dsl.rb
|
120
119
|
- lib/db_leftovers/foreign_key.rb
|
120
|
+
- lib/db_leftovers/generic_database_interface.rb
|
121
121
|
- lib/db_leftovers/index.rb
|
122
|
+
- lib/db_leftovers/mysql_database_interface.rb
|
123
|
+
- lib/db_leftovers/postgres_database_interface.rb
|
122
124
|
- lib/db_leftovers/table_dsl.rb
|
123
125
|
- lib/tasks/leftovers.rake
|
126
|
+
- spec/config/database.yml.sample
|
124
127
|
- spec/db_leftovers_spec.rb
|
128
|
+
- spec/mysql_spec.rb
|
129
|
+
- spec/postgres_spec.rb
|
125
130
|
- spec/spec_helper.rb
|
131
|
+
- spec/support/mock_database_interface.rb
|
132
|
+
- spec/support/shared_db_tests.rb
|
133
|
+
- spec/support/sql_matcher.rb
|
126
134
|
- README.html
|
127
135
|
homepage: http://github.com/pjungwir/db_leftovers
|
128
136
|
licenses:
|
@@ -139,7 +147,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
147
|
version: '0'
|
140
148
|
segments:
|
141
149
|
- 0
|
142
|
-
hash:
|
150
|
+
hash: 3241801701891816563
|
143
151
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
152
|
none: false
|
145
153
|
requirements:
|