db_leftovers 0.7.0 → 0.8.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/README.html +55 -23
- data/README.md +19 -14
- data/TODO +11 -4
- data/VERSION +1 -1
- data/db_leftovers.gemspec +3 -2
- data/lib/db_leftovers/database_interface.rb +18 -9
- data/lib/db_leftovers/dsl.rb +2 -2
- data/spec/db_leftovers_spec.rb +52 -0
- metadata +4 -4
data/README.html
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
<h1>db_leftovers</h1>
|
2
2
|
|
3
|
-
<p>db_leftovers lets you define indexes
|
3
|
+
<p>db_leftovers lets you define indexes, foreign keys, and CHECK constraints for your Rails app
|
4
4
|
in one place using an easy-to-read DSL,
|
5
5
|
then run a rake task to bring your database up-to-date.
|
6
|
-
I wrote this because I didn't want indexes and
|
7
|
-
This was particularly a problem for Heroku projects, because <code>db:push</code> and <code>db:pull</code> do not transfer your foreign keys.
|
6
|
+
I wrote this because I didn't want indexes and constraints scattered throughout my migrations or buried in my <code>schema.rb</code>, and I wanted a command I could run to ensure that they matched across my development, test, staging, and production databases.
|
7
|
+
This was particularly a problem for Heroku projects, because <code>db:push</code> and <code>db:pull</code> do not transfer your foreign keys or other constraints.
|
8
8
|
But now that it's written, I'm finding it useful on non-Heroku projects as well.</p>
|
9
9
|
|
10
10
|
<p>At present db_leftovers works only on PostgreSQL databases,
|
@@ -12,9 +12,9 @@ but it could easily be extended to cover other RDBMSes.</p>
|
|
12
12
|
|
13
13
|
<h2>Configuration File</h2>
|
14
14
|
|
15
|
-
<p>db_leftovers reads a file named <code>config/db_leftovers.rb</code> to find out which indexes and
|
15
|
+
<p>db_leftovers reads a file named <code>config/db_leftovers.rb</code> to find out which indexes and constraints you want in your database. This file is a DSL implemented in Ruby, sort of like <code>config/routes.rb</code>. There are only a few methods:</p>
|
16
16
|
|
17
|
-
<h3>index
|
17
|
+
<h3>index(table_name, columns, [opts])</h3>
|
18
18
|
|
19
19
|
<p>This ensures that you have an index on the given table and column(s). The <code>columns</code> parameter can be either a string or a list of strings. Opts is a hash with the following possible keys:</p>
|
20
20
|
|
@@ -31,38 +31,48 @@ index :books, [:publisher_id, :published_at]
|
|
31
31
|
index :books, :isbn, :unique => true
|
32
32
|
</code></pre>
|
33
33
|
|
34
|
-
<h3>foreign_key
|
34
|
+
<h3>foreign_key(from_table, from_column, to_table, [to_column, [opts]])</h3>
|
35
35
|
|
36
36
|
<p>This ensures that you have a foreign key relating the given tables and columns.
|
37
|
-
All parameters are strings except <code>opts</code>, which is a hash.
|
37
|
+
All parameters are strings/symbols except <code>opts</code>, which is a hash.
|
38
38
|
If you don't pass anything for <code>opts</code>, you can leave off the <code>to_column</code> parameter, and it will default to <code>:id</code>.
|
39
|
-
|
39
|
+
The only option that is supported is <code>:on_delete</code>, which may have any of these values:</p>
|
40
40
|
|
41
41
|
<ul>
|
42
|
+
<li><code>nil</code> Indicates that attempting to delete the referenced row should fail (the default).</li>
|
42
43
|
<li><code>:set_null</code> Indicates that the foreign key should be set to null if the referenced row is deleted.</li>
|
43
44
|
<li><code>:cascade</code> Indicates that the referencing row should be deleted if the referenced row is deleted.</li>
|
44
45
|
</ul>
|
45
46
|
|
46
|
-
<p>These options are mutually exclusive. They should probably be consolidated into a single option like <code>:on_delete</code>.</p>
|
47
|
-
|
48
47
|
<h4>Examples</h4>
|
49
48
|
|
50
49
|
<pre><code>foreign_key :books, :author_id, :authors, :id
|
51
50
|
foreign_key :books, :publisher_id, :publishers
|
52
|
-
foreign_key :pages, :book_id, :books, :id, :
|
51
|
+
foreign_key :pages, :book_id, :books, :id, :on_delete => :cascade
|
53
52
|
</code></pre>
|
54
53
|
|
55
|
-
<h3>
|
54
|
+
<h3>check(constraint_name, on_table, expression)</h3>
|
55
|
+
|
56
|
+
<p>This ensures that you have a CHECK constraint on the given table with the given name and expression.
|
57
|
+
All parameters are strings or symbols.</p>
|
58
|
+
|
59
|
+
<h4>Examples</h4>
|
60
|
+
|
61
|
+
<pre><code>check :books, :books_have_positive_pages, 'page_count > 0'
|
62
|
+
</code></pre>
|
63
|
+
|
64
|
+
<h3>table(table_name, &block)</h3>
|
56
65
|
|
57
66
|
<p>The <code>table</code> call is just a convenience so you can group all a table's indexes and foreign keys together and not keep repeating the table name. You use it like this:</p>
|
58
67
|
|
59
68
|
<pre><code>table :books do
|
60
69
|
index :author_id
|
61
70
|
foreign_key :publisher_id, :publishers
|
71
|
+
check :books_have_positive_pages, 'page_count > 0'
|
62
72
|
end
|
63
73
|
</code></pre>
|
64
74
|
|
65
|
-
<p>You can repeat <code>table</code> calls for the same table several times if you like. This lets you put your indexes in one place and your foreign keys in another.</p>
|
75
|
+
<p>You can repeat <code>table</code> calls for the same table several times if you like. This lets you put your indexes in one place and your foreign keys in another, for example.</p>
|
66
76
|
|
67
77
|
<h2>Running db_leftovers</h2>
|
68
78
|
|
@@ -71,17 +81,39 @@ end
|
|
71
81
|
<pre><code>rake db:leftovers
|
72
82
|
</code></pre>
|
73
83
|
|
84
|
+
<p>db_leftovers will notice whenever an index, foreign key, or CHECK constraint needs to be added, dropped, or changed.
|
85
|
+
It will even catch cases where the name of the managed object is the same, but its attributes have changed.
|
86
|
+
For instance, if you previously required books to have at least 1 page, but now you are introducing a "pamphlet"
|
87
|
+
and want books to have at least 100 pages, you can change your config file to say:</p>
|
88
|
+
|
89
|
+
<pre><code>check :books, :books_have_positive_pages, 'page_count >= 100'
|
90
|
+
</code></pre>
|
91
|
+
|
92
|
+
<p>and db_leftovers will notice the changed expression. It will drop and re-add the constraint.</p>
|
93
|
+
|
94
|
+
<p>One caveat, however: we pull the current expression from the database, and sometimes Postgres does things like
|
95
|
+
add type conversions. If instance, suppose you said <code>check :users, :email_length, 'LENGTH(email) > 2'</code>.
|
96
|
+
The second time you run db_leftovers, it will read the expression from Postgres and get <code>LENGTH((email)::text) > 2</code>,
|
97
|
+
and so it will drop and re-create the constraint.
|
98
|
+
It will drop and re-create it every time you run the rake task.
|
99
|
+
To get around this, make sure your config file uses the same expression as printed by db_leftovers in the rake output.
|
100
|
+
This can also happen for index WHERE clauses, fixable by a similar workaround.</p>
|
101
|
+
|
102
|
+
<p>To print messages even about indexes/foreign keys/constraints that haven't changed, you can say:</p>
|
103
|
+
|
104
|
+
<pre><code>rake db:leftovers VERBOSE=true
|
105
|
+
</code></pre>
|
106
|
+
|
107
|
+
<p>or</p>
|
108
|
+
|
109
|
+
<pre><code>rake db:leftovers DB_LEFTOVERS_VERBOSE=true
|
110
|
+
</code></pre>
|
111
|
+
|
74
112
|
<h2>Known Issues</h2>
|
75
113
|
|
76
114
|
<ul>
|
77
|
-
<li
|
78
|
-
If you want to add support for something else, just send me a pull request!</
|
79
|
-
<li><p>db_leftovers will not notice if an index/foreign key definition changes.
|
80
|
-
Right now it only checks for existence/non-existence.</p></li>
|
81
|
-
<li><p>If your database is mostly up-to-date, then running the Rake task will spam
|
82
|
-
you with messages about how this index and that foreign key already exist.
|
83
|
-
These should be hidden by default and shown only if you request a higher
|
84
|
-
verbosity.</p></li>
|
115
|
+
<li>db_leftovers only supports PostgreSQL databases.
|
116
|
+
If you want to add support for something else, just send me a pull request!</li>
|
85
117
|
</ul>
|
86
118
|
|
87
119
|
<h2>Contributing to db_leftovers</h2>
|
@@ -98,5 +130,5 @@ verbosity.</p></li>
|
|
98
130
|
|
99
131
|
<h2>Copyright</h2>
|
100
132
|
|
101
|
-
<p>Copyright (c) 2012 Paul A. Jungwirth.
|
102
|
-
further details.</p>
|
133
|
+
<p>Copyright (c) 2012 Paul A. Jungwirth.
|
134
|
+
See LICENSE.txt for further details.</p>
|
data/README.md
CHANGED
@@ -78,6 +78,23 @@ db\_leftovers comes with a Rake task named `db:leftovers`. So you can update you
|
|
78
78
|
|
79
79
|
rake db:leftovers
|
80
80
|
|
81
|
+
db\_leftovers will notice whenever an index, foreign key, or CHECK constraint needs to be added, dropped, or changed.
|
82
|
+
It will even catch cases where the name of the managed object is the same, but its attributes have changed.
|
83
|
+
For instance, if you previously required books to have at least 1 page, but now you are introducing a "pamphlet"
|
84
|
+
and want books to have at least 100 pages, you can change your config file to say:
|
85
|
+
|
86
|
+
check :books, :books_have_positive_pages, 'page_count >= 100'
|
87
|
+
|
88
|
+
and db\_leftovers will notice the changed expression. It will drop and re-add the constraint.
|
89
|
+
|
90
|
+
One caveat, however: we pull the current expression from the database, and sometimes Postgres does things like
|
91
|
+
add type conversions. If instance, suppose you said `check :users, :email_length, 'LENGTH(email) > 2'`.
|
92
|
+
The second time you run db\_leftovers, it will read the expression from Postgres and get `LENGTH((email)::text) > 2`,
|
93
|
+
and so it will drop and re-create the constraint.
|
94
|
+
It will drop and re-create it every time you run the rake task.
|
95
|
+
To get around this, make sure your config file uses the same expression as printed by db\_leftovers in the rake output.
|
96
|
+
This can also happen for index WHERE clauses, fixable by a similar workaround.
|
97
|
+
|
81
98
|
To print messages even about indexes/foreign keys/constraints that haven't changed, you can say:
|
82
99
|
|
83
100
|
rake db:leftovers VERBOSE=true
|
@@ -87,26 +104,14 @@ or
|
|
87
104
|
rake db:leftovers DB_LEFTOVERS_VERBOSE=true
|
88
105
|
|
89
106
|
|
107
|
+
|
108
|
+
|
90
109
|
Known Issues
|
91
110
|
------------
|
92
111
|
|
93
112
|
* db\_leftovers only supports PostgreSQL databases.
|
94
113
|
If you want to add support for something else, just send me a pull request!
|
95
114
|
|
96
|
-
* db\_leftovers will not notice if an foreign key/constraint definition changes.
|
97
|
-
Right now it only checks for existence/non-existence.
|
98
|
-
You can get around this by adding a version number to your constraint names,
|
99
|
-
so if you want to force books to have at least 12 pages, you can say this:
|
100
|
-
|
101
|
-
`check :books_have_positive_pages2, 'page_count >= 12'`
|
102
|
-
|
103
|
-
Then the old constraint will be dropped and the new one will be added.
|
104
|
-
|
105
|
-
However, db\_leftovers *does* check for index definitions, so if you
|
106
|
-
make an existing index unique, add a column, remove a WHERE clause, or
|
107
|
-
anything else, it will notice and drop and re-create the index.
|
108
|
-
I'm working on doing the same thing for foreign keys/constraints,
|
109
|
-
but it's not done just yet.
|
110
115
|
|
111
116
|
|
112
117
|
|
data/TODO
CHANGED
@@ -1,7 +1,3 @@
|
|
1
|
-
- Write the README
|
2
|
-
- Support altering index and foreign keys if their definition changes
|
3
|
-
- don't show "{index,fk} already exists" unless you set the verbosity higher
|
4
|
-
|
5
1
|
- Abbreviated notation for conventional foreign keys:
|
6
2
|
|
7
3
|
table :books do
|
@@ -16,3 +12,14 @@
|
|
16
12
|
In other words, the params are foreign_key(from_table, [from_column], to_table, [to_column], [opts]).
|
17
13
|
|
18
14
|
|
15
|
+
- Print the actual WHERE-clause/CHECK expression used by postgres whenever the index/constraint is created (even the first time).
|
16
|
+
|
17
|
+
- Refactor DatabaseInterface so all methods are instance methods and Definition/DSL uses an instance, set according to the Rails database adapter and also settable to a test subclass, so we can change the definition from test to test.
|
18
|
+
|
19
|
+
- Add optional tests that use a real Postgres/MySQL/SQLite if available.
|
20
|
+
- Run all the same tests, but against a real DatabaseInterface instance.
|
21
|
+
|
22
|
+
- Make sure everything works if the current db user has access to multiple databases: only run the code on the db given in the Rails database.yml.
|
23
|
+
|
24
|
+
- Support MySQL
|
25
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.8.0
|
data/db_leftovers.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "db_leftovers"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.8.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Paul A. Jungwirth"]
|
12
|
-
s.date = "2012-09-
|
12
|
+
s.date = "2012-09-30"
|
13
13
|
s.description = " Define indexes and foreign keys for your Rails app\n in one place using an easy-to-read DSL,\n then run a rake task to bring your database up-to-date.\n"
|
14
14
|
s.email = "pj@illuminatedcomputing.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
|
|
25
25
|
"LICENSE.txt",
|
26
26
|
"README.md",
|
27
27
|
"Rakefile",
|
28
|
+
"TODO",
|
28
29
|
"VERSION",
|
29
30
|
"db_leftovers.gemspec",
|
30
31
|
"lib/db_leftovers.rb",
|
@@ -35,7 +35,7 @@ module DBLeftovers
|
|
35
35
|
ORDER BY t.relname, i.relname
|
36
36
|
EOQ
|
37
37
|
ActiveRecord::Base.connection.select_rows(sql).each do |indexrelid, indrelid, table_name, index_name, is_unique, column_numbers, where_clause|
|
38
|
-
where_clause = where_clause
|
38
|
+
where_clause = remove_outer_parens(where_clause) if where_clause
|
39
39
|
ret[index_name] = Index.new(
|
40
40
|
table_name,
|
41
41
|
column_names_for_index(indrelid, column_numbers.split(",")),
|
@@ -97,17 +97,23 @@ module DBLeftovers
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def lookup_all_constraints
|
100
|
+
# TODO: Constraint it to the database for the current Rails project:
|
100
101
|
ret = {}
|
101
102
|
sql = <<-EOQ
|
102
|
-
SELECT
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
103
|
+
SELECT c.conname,
|
104
|
+
t.relname
|
105
|
+
FROM pg_catalog.pg_constraint c,
|
106
|
+
pg_catalog.pg_class t,
|
107
|
+
pg_catalog.pg_namespace n
|
108
|
+
WHERE c.contype = 'c'
|
109
|
+
AND c.conrelid = t.oid
|
110
|
+
AND t.relkind = 'r'
|
111
|
+
AND n.oid = t.relnamespace
|
112
|
+
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
113
|
+
AND pg_catalog.pg_table_is_visible(t.oid)
|
108
114
|
EOQ
|
109
|
-
ActiveRecord::Base.connection.select_rows(sql).each do |constr_name, on_table|
|
110
|
-
ret[constr_name] = Constraint.new(constr_name, on_table,
|
115
|
+
ActiveRecord::Base.connection.select_rows(sql).each do |constr_name, on_table, check_expr|
|
116
|
+
ret[constr_name] = Constraint.new(constr_name, on_table, remove_outer_parens(check_expr))
|
111
117
|
end
|
112
118
|
return ret
|
113
119
|
end
|
@@ -175,6 +181,9 @@ module DBLeftovers
|
|
175
181
|
end
|
176
182
|
end
|
177
183
|
|
184
|
+
def remove_outer_parens(str)
|
185
|
+
str.gsub(/^\((.*)\)$/, '\1')
|
186
|
+
end
|
178
187
|
|
179
188
|
|
180
189
|
end
|
data/lib/db_leftovers/dsl.rb
CHANGED
@@ -112,9 +112,9 @@ module DBLeftovers
|
|
112
112
|
when STATUS_EXISTS
|
113
113
|
puts "Constraint already exists: #{chk.constraint_name} on #{chk.on_table}" if @verbose
|
114
114
|
when STATUS_CHANGED
|
115
|
-
@db.execute_drop_constraint(constraint_name, chk.on_table)
|
115
|
+
@db.execute_drop_constraint(chk.constraint_name, chk.on_table)
|
116
116
|
@db.execute_add_constraint(chk)
|
117
|
-
puts "Dropped & re-created CHECK constraint: #{chk.constraint_name} on #{chk.on_table}"
|
117
|
+
puts "Dropped & re-created CHECK constraint: #{chk.constraint_name} on #{chk.on_table} as #{chk.check}"
|
118
118
|
when STATUS_NEW
|
119
119
|
@db.execute_add_constraint(chk)
|
120
120
|
puts "Created CHECK constraint: #{chk.constraint_name} on #{chk.on_table}"
|
data/spec/db_leftovers_spec.rb
CHANGED
@@ -425,4 +425,56 @@ describe DBLeftovers do
|
|
425
425
|
}.should raise_error(RuntimeError, "`:cascade => true` should now be `:on_delete => :cascade`")
|
426
426
|
end
|
427
427
|
|
428
|
+
it "should create CHECK constraints on an empty database" do
|
429
|
+
DBLeftovers::DatabaseInterface.starts_with([], [], [])
|
430
|
+
DBLeftovers::Definition.define do
|
431
|
+
check :books, :books_have_positive_pages, 'pages_count > 0'
|
432
|
+
end
|
433
|
+
DBLeftovers::DatabaseInterface.sqls.should have(1).item
|
434
|
+
DBLeftovers::DatabaseInterface.should have_seen_sql <<-EOQ
|
435
|
+
ALTER TABLE books ADD CONSTRAINT books_have_positive_pages CHECK (pages_count > 0)
|
436
|
+
EOQ
|
437
|
+
end
|
438
|
+
|
439
|
+
it "should create CHECK constraints inside a table block" do
|
440
|
+
DBLeftovers::DatabaseInterface.starts_with([], [], [])
|
441
|
+
DBLeftovers::Definition.define do
|
442
|
+
table :books do
|
443
|
+
check :books_have_positive_pages, 'pages_count > 0'
|
444
|
+
end
|
445
|
+
end
|
446
|
+
DBLeftovers::DatabaseInterface.sqls.should have(1).item
|
447
|
+
DBLeftovers::DatabaseInterface.should have_seen_sql <<-EOQ
|
448
|
+
ALTER TABLE books ADD CONSTRAINT books_have_positive_pages CHECK (pages_count > 0)
|
449
|
+
EOQ
|
450
|
+
end
|
451
|
+
|
452
|
+
it "should remove obsolete CHECK constraints" do
|
453
|
+
DBLeftovers::DatabaseInterface.starts_with([], [], [
|
454
|
+
DBLeftovers::Constraint.new(:books_have_positive_pages, :books, 'pages_count > 0')
|
455
|
+
])
|
456
|
+
DBLeftovers::Definition.define do
|
457
|
+
end
|
458
|
+
DBLeftovers::DatabaseInterface.sqls.should have(1).item
|
459
|
+
DBLeftovers::DatabaseInterface.should have_seen_sql <<-EOQ
|
460
|
+
ALTER TABLE books DROP CONSTRAINT books_have_positive_pages
|
461
|
+
EOQ
|
462
|
+
end
|
463
|
+
|
464
|
+
it "should drop and re-create changed CHECK constraints" do
|
465
|
+
DBLeftovers::DatabaseInterface.starts_with([], [], [
|
466
|
+
DBLeftovers::Constraint.new(:books_have_positive_pages, :books, 'pages_count > 0')
|
467
|
+
])
|
468
|
+
DBLeftovers::Definition.define do
|
469
|
+
check :books, :books_have_positive_pages, 'pages_count > 12'
|
470
|
+
end
|
471
|
+
DBLeftovers::DatabaseInterface.sqls.should have(2).items
|
472
|
+
DBLeftovers::DatabaseInterface.should have_seen_sql <<-EOQ
|
473
|
+
ALTER TABLE books DROP CONSTRAINT books_have_positive_pages
|
474
|
+
EOQ
|
475
|
+
DBLeftovers::DatabaseInterface.should have_seen_sql <<-EOQ
|
476
|
+
ALTER TABLE books ADD CONSTRAINT books_have_positive_pages CHECK (pages_count > 12)
|
477
|
+
EOQ
|
478
|
+
end
|
479
|
+
|
428
480
|
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: 0.8.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-
|
12
|
+
date: 2012-09-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -109,6 +109,7 @@ files:
|
|
109
109
|
- LICENSE.txt
|
110
110
|
- README.md
|
111
111
|
- Rakefile
|
112
|
+
- TODO
|
112
113
|
- VERSION
|
113
114
|
- db_leftovers.gemspec
|
114
115
|
- lib/db_leftovers.rb
|
@@ -123,7 +124,6 @@ files:
|
|
123
124
|
- spec/db_leftovers_spec.rb
|
124
125
|
- spec/spec_helper.rb
|
125
126
|
- README.html
|
126
|
-
- TODO
|
127
127
|
homepage: http://github.com/pjungwir/db_leftovers
|
128
128
|
licenses:
|
129
129
|
- MIT
|
@@ -139,7 +139,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
139
|
version: '0'
|
140
140
|
segments:
|
141
141
|
- 0
|
142
|
-
hash:
|
142
|
+
hash: 856836431
|
143
143
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
144
|
none: false
|
145
145
|
requirements:
|