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.
@@ -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
@@ -0,0 +1,11 @@
1
+
2
+ RSpec::Matchers.define :have_seen_sql do |sql|
3
+ match do |db|
4
+ db.saw_sql(sql)
5
+ end
6
+
7
+ failure_message_for_should do |db|
8
+ "Should have seen...\n#{sql}\n...but instead saw...\n#{db.sqls.join("\n.....\n")}"
9
+ end
10
+ end
11
+
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.9.2
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-09-30 00:00:00.000000000 Z
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: 269207025
150
+ hash: 3241801701891816563
143
151
  required_rubygems_version: !ruby/object:Gem::Requirement
144
152
  none: false
145
153
  requirements: