db_leftovers 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.html +102 -0
- data/README.md +104 -0
- data/TODO +3 -0
- data/VERSION +1 -1
- data/db_leftovers.gemspec +11 -4
- data/lib/db_leftovers/database_interface.rb +88 -0
- data/lib/db_leftovers/definition.rb +16 -0
- data/lib/db_leftovers/dsl.rb +0 -177
- data/lib/db_leftovers/foreign_key.rb +27 -0
- data/lib/db_leftovers/index.rb +35 -0
- data/lib/db_leftovers/table_dsl.rb +22 -0
- data/lib/db_leftovers.rb +5 -0
- metadata +24 -15
- data/README.rdoc +0 -19
data/README.html
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
<h1>db_leftovers</h1>
|
2
|
+
|
3
|
+
<p>db_leftovers lets you define indexes and foreign keys for your Rails app
|
4
|
+
in one place using an easy-to-read DSL,
|
5
|
+
then run a rake task to bring your database up-to-date.
|
6
|
+
I wrote this because I didn't want indexes and foreign keys 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.
|
8
|
+
But now that it's written, I'm finding it useful on non-Heroku projects as well.</p>
|
9
|
+
|
10
|
+
<p>At present db_leftovers works only on PostgreSQL databases,
|
11
|
+
but it could easily be extended to cover other RDBMSes.</p>
|
12
|
+
|
13
|
+
<h2>Configuration File</h2>
|
14
|
+
|
15
|
+
<p>db_leftovers reads a file named <code>config/db_leftovers.rb</code> to find out which indexes and foreign keys 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
|
+
|
17
|
+
<h3>index table_name, columns, [opts]</h3>
|
18
|
+
|
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
|
+
|
21
|
+
<ul>
|
22
|
+
<li><p><code>:name</code> The name of the index. Defaults to <code>index_</code><em>table_name</em><code>_on_</code><em>column_names</em>, like the <code>add_index</code> method from Rails migrations.</p></li>
|
23
|
+
<li><p><code>:unique</code> Set this to <code>true</code> if you'd like a unique index.</p></li>
|
24
|
+
<li><p><code>:where</code> Accepts SQL to include in the <code>WHERE</code> part of the <code>CREATE INDEX</code> command, in case you want to limit the index to a subset of the table's rows.</p></li>
|
25
|
+
</ul>
|
26
|
+
|
27
|
+
<h4>Examples</h4>
|
28
|
+
|
29
|
+
<pre><code>index :books, :author_id
|
30
|
+
index :books, [:publisher_id, :published_at]
|
31
|
+
index :books, :isbn, :unique => true
|
32
|
+
</code></pre>
|
33
|
+
|
34
|
+
<h3>foreign_key from_table, from_column, to_table, [to_column, [opts]]</h3>
|
35
|
+
|
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.
|
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
|
+
These options are supported:</p>
|
40
|
+
|
41
|
+
<ul>
|
42
|
+
<li><code>:set_null</code> Indicates that the foreign key should be set to null if the referenced row is deleted.</li>
|
43
|
+
<li><code>:cascade</code> Indicates that the referencing row should be deleted if the referenced row is deleted.</li>
|
44
|
+
</ul>
|
45
|
+
|
46
|
+
<p>These options are mutually exclusive. They should probably be consolidated into a single option like <code>:on_delete</code>.</p>
|
47
|
+
|
48
|
+
<h4>Examples</h4>
|
49
|
+
|
50
|
+
<pre><code>foreign_key :books, :author_id, :authors, :id
|
51
|
+
foreign_key :books, :publisher_id, :publishers
|
52
|
+
foreign_key :pages, :book_id, :books, :id, :cascade => true
|
53
|
+
</code></pre>
|
54
|
+
|
55
|
+
<h3>table table_name, &block</h3>
|
56
|
+
|
57
|
+
<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
|
+
|
59
|
+
<pre><code>table :books do
|
60
|
+
index :author_id
|
61
|
+
foreign_key :publisher_id, :publishers
|
62
|
+
end
|
63
|
+
</code></pre>
|
64
|
+
|
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>
|
66
|
+
|
67
|
+
<h2>Running db_leftovers</h2>
|
68
|
+
|
69
|
+
<p>db_leftovers comes with a Rake task named <code>db:leftovers</code>. So you can update your database to match your config file by saying this:</p>
|
70
|
+
|
71
|
+
<pre><code>rake db:leftovers
|
72
|
+
</code></pre>
|
73
|
+
|
74
|
+
<h2>Known Issues</h2>
|
75
|
+
|
76
|
+
<ul>
|
77
|
+
<li><p>db_leftovers only supports PostgreSQL databases.
|
78
|
+
If you want to add support for something else, just send me a pull request!</p></li>
|
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>
|
85
|
+
</ul>
|
86
|
+
|
87
|
+
<h2>Contributing to db_leftovers</h2>
|
88
|
+
|
89
|
+
<ul>
|
90
|
+
<li>Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet</li>
|
91
|
+
<li>Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it</li>
|
92
|
+
<li>Fork the project</li>
|
93
|
+
<li>Start a feature/bugfix branch</li>
|
94
|
+
<li>Commit and push until you are happy with your contribution</li>
|
95
|
+
<li>Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.</li>
|
96
|
+
<li>Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.</li>
|
97
|
+
</ul>
|
98
|
+
|
99
|
+
<h2>Copyright</h2>
|
100
|
+
|
101
|
+
<p>Copyright (c) 2012 Paul A. Jungwirth. See LICENSE.txt for
|
102
|
+
further details.</p>
|
data/README.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
db\_leftovers
|
2
|
+
=============
|
3
|
+
|
4
|
+
db\_leftovers lets you define indexes and foreign keys for your Rails app
|
5
|
+
in one place using an easy-to-read DSL,
|
6
|
+
then run a rake task to bring your database up-to-date.
|
7
|
+
I wrote this because I didn't want indexes and foreign keys scattered throughout my migrations or buried in my `schema.rb`, and I wanted a command I could run to ensure that they matched across my development, test, staging, and production databases.
|
8
|
+
This was particularly a problem for Heroku projects, because `db:push` and `db:pull` do not transfer your foreign keys.
|
9
|
+
But now that it's written, I'm finding it useful on non-Heroku projects as well.
|
10
|
+
|
11
|
+
At present db\_leftovers works only on PostgreSQL databases,
|
12
|
+
but it could easily be extended to cover other RDBMSes.
|
13
|
+
|
14
|
+
Configuration File
|
15
|
+
------------------
|
16
|
+
|
17
|
+
db\_leftovers reads a file named `config/db_leftovers.rb` to find out which indexes and foreign keys you want in your database. This file is a DSL implemented in Ruby, sort of like `config/routes.rb`. There are only a few methods:
|
18
|
+
|
19
|
+
### index table\_name, columns, [opts]
|
20
|
+
|
21
|
+
This ensures that you have an index on the given table and column(s). The `columns` parameter can be either a string or a list of strings. Opts is a hash with the following possible keys:
|
22
|
+
|
23
|
+
* `:name` The name of the index. Defaults to `index_`*table\_name*`_on_`*column\_names*, like the `add_index` method from Rails migrations.
|
24
|
+
|
25
|
+
* `:unique` Set this to `true` if you'd like a unique index.
|
26
|
+
|
27
|
+
* `:where` Accepts SQL to include in the `WHERE` part of the `CREATE INDEX` command, in case you want to limit the index to a subset of the table's rows.
|
28
|
+
|
29
|
+
#### Examples
|
30
|
+
|
31
|
+
index :books, :author_id
|
32
|
+
index :books, [:publisher_id, :published_at]
|
33
|
+
index :books, :isbn, :unique => true
|
34
|
+
|
35
|
+
### foreign\_key from\_table, from\_column, to\_table, [to\_column, [opts]]
|
36
|
+
|
37
|
+
This ensures that you have a foreign key relating the given tables and columns.
|
38
|
+
All parameters are strings except `opts`, which is a hash.
|
39
|
+
If you don't pass anything for `opts`, you can leave off the `to_column` parameter, and it will default to `:id`.
|
40
|
+
These options are supported:
|
41
|
+
|
42
|
+
* `:set_null` Indicates that the foreign key should be set to null if the referenced row is deleted.
|
43
|
+
* `:cascade` Indicates that the referencing row should be deleted if the referenced row is deleted.
|
44
|
+
|
45
|
+
These options are mutually exclusive. They should probably be consolidated into a single option like `:on_delete`.
|
46
|
+
|
47
|
+
#### Examples
|
48
|
+
|
49
|
+
foreign_key :books, :author_id, :authors, :id
|
50
|
+
foreign_key :books, :publisher_id, :publishers
|
51
|
+
foreign_key :pages, :book_id, :books, :id, :cascade => true
|
52
|
+
|
53
|
+
### table table\_name, &block
|
54
|
+
|
55
|
+
The `table` 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:
|
56
|
+
|
57
|
+
table :books do
|
58
|
+
index :author_id
|
59
|
+
foreign_key :publisher_id, :publishers
|
60
|
+
end
|
61
|
+
|
62
|
+
You can repeat `table` 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.
|
63
|
+
|
64
|
+
|
65
|
+
Running db\_leftovers
|
66
|
+
---------------------
|
67
|
+
|
68
|
+
db\_leftovers comes with a Rake task named `db:leftovers`. So you can update your database to match your config file by saying this:
|
69
|
+
|
70
|
+
rake db:leftovers
|
71
|
+
|
72
|
+
|
73
|
+
Known Issues
|
74
|
+
------------
|
75
|
+
|
76
|
+
* db\_leftovers only supports PostgreSQL databases.
|
77
|
+
If you want to add support for something else, just send me a pull request!
|
78
|
+
|
79
|
+
* db\_leftovers will not notice if an index/foreign key definition changes.
|
80
|
+
Right now it only checks for existence/non-existence.
|
81
|
+
|
82
|
+
* If your database is mostly up-to-date, then running the Rake task will spam
|
83
|
+
you with messages about how this index and that foreign key already exist.
|
84
|
+
These should be hidden by default and shown only if you request a higher
|
85
|
+
verbosity.
|
86
|
+
|
87
|
+
|
88
|
+
Contributing to db\_leftovers
|
89
|
+
-----------------------------
|
90
|
+
|
91
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
92
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
93
|
+
* Fork the project
|
94
|
+
* Start a feature/bugfix branch
|
95
|
+
* Commit and push until you are happy with your contribution
|
96
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
97
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
98
|
+
|
99
|
+
Copyright
|
100
|
+
---------
|
101
|
+
|
102
|
+
Copyright (c) 2012 Paul A. Jungwirth. See LICENSE.txt for
|
103
|
+
further details.
|
104
|
+
|
data/TODO
ADDED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/db_leftovers.gemspec
CHANGED
@@ -5,28 +5,35 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "db_leftovers"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.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-01-
|
12
|
+
s.date = "2012-01-20"
|
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 = [
|
16
16
|
"LICENSE.txt",
|
17
|
-
"README.
|
17
|
+
"README.html",
|
18
|
+
"README.md",
|
19
|
+
"TODO"
|
18
20
|
]
|
19
21
|
s.files = [
|
20
22
|
".document",
|
21
23
|
"Gemfile",
|
22
24
|
"Gemfile.lock",
|
23
25
|
"LICENSE.txt",
|
24
|
-
"README.
|
26
|
+
"README.md",
|
25
27
|
"Rakefile",
|
26
28
|
"VERSION",
|
27
29
|
"db_leftovers.gemspec",
|
28
30
|
"lib/db_leftovers.rb",
|
31
|
+
"lib/db_leftovers/database_interface.rb",
|
32
|
+
"lib/db_leftovers/definition.rb",
|
29
33
|
"lib/db_leftovers/dsl.rb",
|
34
|
+
"lib/db_leftovers/foreign_key.rb",
|
35
|
+
"lib/db_leftovers/index.rb",
|
36
|
+
"lib/db_leftovers/table_dsl.rb",
|
30
37
|
"lib/tasks/leftovers.rake",
|
31
38
|
"spec/db_leftovers_spec.rb",
|
32
39
|
"spec/spec_helper.rb"
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module DBLeftovers
|
2
|
+
|
3
|
+
class DatabaseInterface
|
4
|
+
|
5
|
+
def lookup_all_indexes
|
6
|
+
ret = {}
|
7
|
+
sql = <<-EOQ
|
8
|
+
SELECT n.nspname as "Schema", c.relname as "Name",
|
9
|
+
CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' END as "Type",
|
10
|
+
u.usename as "Owner",
|
11
|
+
c2.relname as "Table"
|
12
|
+
FROM pg_catalog.pg_class c
|
13
|
+
JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid
|
14
|
+
JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid
|
15
|
+
LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
|
16
|
+
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
17
|
+
WHERE c.relkind IN ('i','')
|
18
|
+
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
19
|
+
AND pg_catalog.pg_table_is_visible(c.oid)
|
20
|
+
AND c.relname NOT LIKE '%_pkey'
|
21
|
+
AND c2.relname NOT IN ('delayed_jobs', 'schema_migrations')
|
22
|
+
ORDER BY 1,2;
|
23
|
+
EOQ
|
24
|
+
ActiveRecord::Base.connection.select_rows(sql).each do |schema, index_name, object_type, owner, table_name|
|
25
|
+
ret[index_name] = table_name
|
26
|
+
end
|
27
|
+
return ret
|
28
|
+
end
|
29
|
+
|
30
|
+
def lookup_all_foreign_keys
|
31
|
+
ret = {}
|
32
|
+
sql = <<-EOQ
|
33
|
+
SELECT t.constraint_name, t.table_name, k.column_name, t.constraint_type, c.table_name, c.column_name
|
34
|
+
FROM information_schema.table_constraints t,
|
35
|
+
information_schema.constraint_column_usage c,
|
36
|
+
information_schema.key_column_usage k
|
37
|
+
WHERE t.constraint_name = c.constraint_name
|
38
|
+
AND k.constraint_name = c.constraint_name
|
39
|
+
AND t.constraint_type = 'FOREIGN KEY'
|
40
|
+
EOQ
|
41
|
+
ActiveRecord::Base.connection.select_rows(sql).each do |constr_name, from_table, from_column, constr_type, to_table, to_column|
|
42
|
+
ret[constr_name] = ForeignKey.new(constr_name, from_table, from_column, to_table, to_column)
|
43
|
+
end
|
44
|
+
return ret
|
45
|
+
end
|
46
|
+
|
47
|
+
def execute_add_index(idx)
|
48
|
+
unique = idx.unique? ? 'UNIQUE' : ''
|
49
|
+
where = idx.where_clause.present? ? "WHERE #{idx.where_clause}" : ''
|
50
|
+
|
51
|
+
sql = <<-EOQ
|
52
|
+
CREATE #{unique} INDEX #{idx.index_name}
|
53
|
+
ON #{idx.table_name}
|
54
|
+
(#{idx.column_names.join(', ')})
|
55
|
+
#{where}
|
56
|
+
EOQ
|
57
|
+
execute_sql(sql)
|
58
|
+
end
|
59
|
+
|
60
|
+
def execute_drop_index(table_name, index_name)
|
61
|
+
sql = <<-EOQ
|
62
|
+
DROP INDEX #{index_name}
|
63
|
+
EOQ
|
64
|
+
execute_sql(sql)
|
65
|
+
end
|
66
|
+
|
67
|
+
def execute_add_foreign_key(fk)
|
68
|
+
on_delete = "ON DELETE CASCADE" if fk.cascade
|
69
|
+
on_delete = "ON DELETE SET NULL" if fk.set_null
|
70
|
+
execute_sql %{ALTER TABLE #{fk.from_table}
|
71
|
+
ADD CONSTRAINT #{fk.constraint_name}
|
72
|
+
FOREIGN KEY (#{fk.from_column})
|
73
|
+
REFERENCES #{fk.to_table} (#{fk.to_column})
|
74
|
+
#{on_delete}}
|
75
|
+
end
|
76
|
+
|
77
|
+
def execute_drop_foreign_key(constraint_name, from_table, from_column)
|
78
|
+
execute_sql %{ALTER TABLE #{from_table}
|
79
|
+
DROP CONSTRAINT #{constraint_name}}
|
80
|
+
end
|
81
|
+
|
82
|
+
def execute_sql(sql)
|
83
|
+
ActiveRecord::Base.connection.execute(sql)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module DBLeftovers
|
2
|
+
|
3
|
+
class Definition
|
4
|
+
def self.define(opts={}, &block)
|
5
|
+
opts = {
|
6
|
+
:do_indexes => true,
|
7
|
+
:do_foreign_keys => true
|
8
|
+
}.merge(opts)
|
9
|
+
dsl = DSL.new
|
10
|
+
dsl.define(&block)
|
11
|
+
dsl.record_indexes if opts[:do_indexes]
|
12
|
+
dsl.record_foreign_keys if opts[:do_foreign_keys]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/lib/db_leftovers/dsl.rb
CHANGED
@@ -1,97 +1,5 @@
|
|
1
1
|
module DBLeftovers
|
2
2
|
|
3
|
-
class Definition
|
4
|
-
def self.define(opts={}, &block)
|
5
|
-
opts = {
|
6
|
-
:do_indexes => true,
|
7
|
-
:do_foreign_keys => true
|
8
|
-
}.merge(opts)
|
9
|
-
dsl = DSL.new
|
10
|
-
dsl.define(&block)
|
11
|
-
dsl.record_indexes if opts[:do_indexes]
|
12
|
-
dsl.record_foreign_keys if opts[:do_foreign_keys]
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
# Just a struct to hold all the info for one index:
|
20
|
-
class Index
|
21
|
-
attr_accessor :table_name, :column_names, :index_name,
|
22
|
-
:where_clause, :unique
|
23
|
-
|
24
|
-
def initialize(table_name, column_names, opts={})
|
25
|
-
opts = {
|
26
|
-
:where => nil,
|
27
|
-
:unique => false,
|
28
|
-
}.merge(opts)
|
29
|
-
opts.keys.each do |k|
|
30
|
-
raise "Unknown option: #{k}" unless [:where, :unique, :name].include?(k)
|
31
|
-
end
|
32
|
-
@table_name = table_name.to_s
|
33
|
-
@column_names = [column_names].flatten.map{|x| x.to_s}
|
34
|
-
@where_clause = opts[:where]
|
35
|
-
@unique = opts[:unique]
|
36
|
-
@index_name = opts[:name] || choose_name(@table_name, @column_names)
|
37
|
-
end
|
38
|
-
|
39
|
-
def unique?
|
40
|
-
@unique
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def choose_name(table_name, column_names)
|
46
|
-
"index_#{table_name}_on_#{column_names.join('_and_')}"
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
|
52
|
-
class ForeignKey
|
53
|
-
attr_accessor :constraint_name, :from_table, :from_column, :to_table, :to_column, :set_null, :cascade
|
54
|
-
|
55
|
-
def initialize(constraint_name, from_table, from_column, to_table, to_column, opts={})
|
56
|
-
opts = {
|
57
|
-
:set_null => false,
|
58
|
-
:cascade => false
|
59
|
-
}.merge(opts)
|
60
|
-
opts.keys.each do |k|
|
61
|
-
raise "Unknown option: #{k}" unless [:set_null, :cascade].include?(k)
|
62
|
-
end
|
63
|
-
@constraint_name = constraint_name
|
64
|
-
@from_table = from_table
|
65
|
-
@from_column = from_column
|
66
|
-
@to_table = to_table
|
67
|
-
@to_column = to_column
|
68
|
-
|
69
|
-
@set_null = opts[:set_null]
|
70
|
-
@cascade = opts[:cascade]
|
71
|
-
|
72
|
-
raise "ON DELETE can't be both set_null and cascade" if @set_null and @cascade
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
class TableDSL
|
77
|
-
def initialize(dsl, table_name)
|
78
|
-
@dsl = dsl
|
79
|
-
@table_name = table_name
|
80
|
-
end
|
81
|
-
|
82
|
-
def define(&block)
|
83
|
-
instance_eval(&block)
|
84
|
-
end
|
85
|
-
|
86
|
-
def index(column_names, opts={})
|
87
|
-
@dsl.index(@table_name, column_names, opts)
|
88
|
-
end
|
89
|
-
|
90
|
-
def foreign_key(from_column, to_table, to_column='id', opts={})
|
91
|
-
@dsl.foreign_key(@table_name, from_column, to_table, to_column, opts)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
3
|
class DSL
|
96
4
|
def initialize
|
97
5
|
@db = DatabaseInterface.new
|
@@ -201,89 +109,4 @@ module DBLeftovers
|
|
201
109
|
|
202
110
|
end
|
203
111
|
|
204
|
-
class DatabaseInterface
|
205
|
-
|
206
|
-
def lookup_all_indexes
|
207
|
-
ret = {}
|
208
|
-
sql = <<-EOQ
|
209
|
-
SELECT n.nspname as "Schema", c.relname as "Name",
|
210
|
-
CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' END as "Type",
|
211
|
-
u.usename as "Owner",
|
212
|
-
c2.relname as "Table"
|
213
|
-
FROM pg_catalog.pg_class c
|
214
|
-
JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid
|
215
|
-
JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid
|
216
|
-
LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
|
217
|
-
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
218
|
-
WHERE c.relkind IN ('i','')
|
219
|
-
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
220
|
-
AND pg_catalog.pg_table_is_visible(c.oid)
|
221
|
-
AND c.relname NOT LIKE '%_pkey'
|
222
|
-
AND c2.relname NOT IN ('delayed_jobs', 'schema_migrations')
|
223
|
-
ORDER BY 1,2;
|
224
|
-
EOQ
|
225
|
-
ActiveRecord::Base.connection.select_rows(sql).each do |schema, index_name, object_type, owner, table_name|
|
226
|
-
ret[index_name] = table_name
|
227
|
-
end
|
228
|
-
return ret
|
229
|
-
end
|
230
|
-
|
231
|
-
def lookup_all_foreign_keys
|
232
|
-
ret = {}
|
233
|
-
sql = <<-EOQ
|
234
|
-
SELECT t.constraint_name, t.table_name, k.column_name, t.constraint_type, c.table_name, c.column_name
|
235
|
-
FROM information_schema.table_constraints t,
|
236
|
-
information_schema.constraint_column_usage c,
|
237
|
-
information_schema.key_column_usage k
|
238
|
-
WHERE t.constraint_name = c.constraint_name
|
239
|
-
AND k.constraint_name = c.constraint_name
|
240
|
-
AND t.constraint_type = 'FOREIGN KEY'
|
241
|
-
EOQ
|
242
|
-
ActiveRecord::Base.connection.select_rows(sql).each do |constr_name, from_table, from_column, constr_type, to_table, to_column|
|
243
|
-
ret[constr_name] = ForeignKey.new(constr_name, from_table, from_column, to_table, to_column)
|
244
|
-
end
|
245
|
-
return ret
|
246
|
-
end
|
247
|
-
|
248
|
-
def execute_add_index(idx)
|
249
|
-
unique = idx.unique? ? 'UNIQUE' : ''
|
250
|
-
where = idx.where_clause.present? ? "WHERE #{idx.where_clause}" : ''
|
251
|
-
|
252
|
-
sql = <<-EOQ
|
253
|
-
CREATE #{unique} INDEX #{idx.index_name}
|
254
|
-
ON #{idx.table_name}
|
255
|
-
(#{idx.column_names.join(', ')})
|
256
|
-
#{where}
|
257
|
-
EOQ
|
258
|
-
execute_sql(sql)
|
259
|
-
end
|
260
|
-
|
261
|
-
def execute_drop_index(table_name, index_name)
|
262
|
-
sql = <<-EOQ
|
263
|
-
DROP INDEX #{index_name}
|
264
|
-
EOQ
|
265
|
-
execute_sql(sql)
|
266
|
-
end
|
267
|
-
|
268
|
-
def execute_add_foreign_key(fk)
|
269
|
-
on_delete = "ON DELETE CASCADE" if fk.cascade
|
270
|
-
on_delete = "ON DELETE SET NULL" if fk.set_null
|
271
|
-
execute_sql %{ALTER TABLE #{fk.from_table}
|
272
|
-
ADD CONSTRAINT #{fk.constraint_name}
|
273
|
-
FOREIGN KEY (#{fk.from_column})
|
274
|
-
REFERENCES #{fk.to_table} (#{fk.to_column})
|
275
|
-
#{on_delete}}
|
276
|
-
end
|
277
|
-
|
278
|
-
def execute_drop_foreign_key(constraint_name, from_table, from_column)
|
279
|
-
execute_sql %{ALTER TABLE #{from_table}
|
280
|
-
DROP CONSTRAINT #{constraint_name}}
|
281
|
-
end
|
282
|
-
|
283
|
-
def execute_sql(sql)
|
284
|
-
ActiveRecord::Base.connection.execute(sql)
|
285
|
-
end
|
286
|
-
|
287
|
-
end
|
288
|
-
|
289
112
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module DBLeftovers
|
2
|
+
|
3
|
+
class ForeignKey
|
4
|
+
attr_accessor :constraint_name, :from_table, :from_column, :to_table, :to_column, :set_null, :cascade
|
5
|
+
|
6
|
+
def initialize(constraint_name, from_table, from_column, to_table, to_column, opts={})
|
7
|
+
opts = {
|
8
|
+
:set_null => false,
|
9
|
+
:cascade => false
|
10
|
+
}.merge(opts)
|
11
|
+
opts.keys.each do |k|
|
12
|
+
raise "Unknown option: #{k}" unless [:set_null, :cascade].include?(k)
|
13
|
+
end
|
14
|
+
@constraint_name = constraint_name
|
15
|
+
@from_table = from_table
|
16
|
+
@from_column = from_column
|
17
|
+
@to_table = to_table
|
18
|
+
@to_column = to_column
|
19
|
+
|
20
|
+
@set_null = opts[:set_null]
|
21
|
+
@cascade = opts[:cascade]
|
22
|
+
|
23
|
+
raise "ON DELETE can't be both set_null and cascade" if @set_null and @cascade
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module DBLeftovers
|
2
|
+
|
3
|
+
# Just a struct to hold all the info for one index:
|
4
|
+
class Index
|
5
|
+
attr_accessor :table_name, :column_names, :index_name,
|
6
|
+
:where_clause, :unique
|
7
|
+
|
8
|
+
def initialize(table_name, column_names, opts={})
|
9
|
+
opts = {
|
10
|
+
:where => nil,
|
11
|
+
:unique => false,
|
12
|
+
}.merge(opts)
|
13
|
+
opts.keys.each do |k|
|
14
|
+
raise "Unknown option: #{k}" unless [:where, :unique, :name].include?(k)
|
15
|
+
end
|
16
|
+
@table_name = table_name.to_s
|
17
|
+
@column_names = [column_names].flatten.map{|x| x.to_s}
|
18
|
+
@where_clause = opts[:where]
|
19
|
+
@unique = opts[:unique]
|
20
|
+
@index_name = opts[:name] || choose_name(@table_name, @column_names)
|
21
|
+
end
|
22
|
+
|
23
|
+
def unique?
|
24
|
+
@unique
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def choose_name(table_name, column_names)
|
30
|
+
"index_#{table_name}_on_#{column_names.join('_and_')}"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module DBLeftovers
|
2
|
+
|
3
|
+
class TableDSL
|
4
|
+
def initialize(dsl, table_name)
|
5
|
+
@dsl = dsl
|
6
|
+
@table_name = table_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def define(&block)
|
10
|
+
instance_eval(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def index(column_names, opts={})
|
14
|
+
@dsl.index(@table_name, column_names, opts)
|
15
|
+
end
|
16
|
+
|
17
|
+
def foreign_key(from_column, to_table, to_column='id', opts={})
|
18
|
+
@dsl.foreign_key(@table_name, from_column, to_table, to_column, opts)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/lib/db_leftovers.rb
CHANGED
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.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-01-
|
12
|
+
date: 2012-01-20 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
16
|
-
requirement: &
|
16
|
+
requirement: &80697560 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 3.0.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *80697560
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &80697230 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 2.3.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *80697230
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: bundler
|
38
|
-
requirement: &
|
38
|
+
requirement: &80696850 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 1.0.0
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *80696850
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: jeweler
|
49
|
-
requirement: &
|
49
|
+
requirement: &80691820 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 1.6.4
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *80691820
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rcov
|
60
|
-
requirement: &
|
60
|
+
requirement: &80691390 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,7 +65,7 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *80691390
|
69
69
|
description: ! " Define indexes and foreign keys for your Rails app\n in
|
70
70
|
one place using an easy-to-read DSL,\n then run a rake task to bring your
|
71
71
|
database up-to-date.\n"
|
@@ -74,21 +74,30 @@ executables: []
|
|
74
74
|
extensions: []
|
75
75
|
extra_rdoc_files:
|
76
76
|
- LICENSE.txt
|
77
|
-
- README.
|
77
|
+
- README.html
|
78
|
+
- README.md
|
79
|
+
- TODO
|
78
80
|
files:
|
79
81
|
- .document
|
80
82
|
- Gemfile
|
81
83
|
- Gemfile.lock
|
82
84
|
- LICENSE.txt
|
83
|
-
- README.
|
85
|
+
- README.md
|
84
86
|
- Rakefile
|
85
87
|
- VERSION
|
86
88
|
- db_leftovers.gemspec
|
87
89
|
- lib/db_leftovers.rb
|
90
|
+
- lib/db_leftovers/database_interface.rb
|
91
|
+
- lib/db_leftovers/definition.rb
|
88
92
|
- lib/db_leftovers/dsl.rb
|
93
|
+
- lib/db_leftovers/foreign_key.rb
|
94
|
+
- lib/db_leftovers/index.rb
|
95
|
+
- lib/db_leftovers/table_dsl.rb
|
89
96
|
- lib/tasks/leftovers.rake
|
90
97
|
- spec/db_leftovers_spec.rb
|
91
98
|
- spec/spec_helper.rb
|
99
|
+
- README.html
|
100
|
+
- TODO
|
92
101
|
homepage: http://github.com/pjungwir/db_leftovers
|
93
102
|
licenses:
|
94
103
|
- MIT
|
@@ -104,7 +113,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
113
|
version: '0'
|
105
114
|
segments:
|
106
115
|
- 0
|
107
|
-
hash:
|
116
|
+
hash: -934339327
|
108
117
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
118
|
none: false
|
110
119
|
requirements:
|
data/README.rdoc
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
= db_leftovers
|
2
|
-
|
3
|
-
Description goes here.
|
4
|
-
|
5
|
-
== Contributing to db_leftovers
|
6
|
-
|
7
|
-
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
8
|
-
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
9
|
-
* Fork the project
|
10
|
-
* Start a feature/bugfix branch
|
11
|
-
* Commit and push until you are happy with your contribution
|
12
|
-
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
-
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
-
|
15
|
-
== Copyright
|
16
|
-
|
17
|
-
Copyright (c) 2012 Paul A. Jungwirth. See LICENSE.txt for
|
18
|
-
further details.
|
19
|
-
|