db_leftovers 0.9.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: