peekdb 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rvmrc +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +28 -0
- data/README.md +23 -0
- data/Rakefile +8 -0
- data/bin/peekdb +10 -0
- data/lib/peekdb/database.rb +36 -0
- data/lib/peekdb/database_mysql.rb +2 -0
- data/lib/peekdb/database_psql.rb +57 -0
- data/lib/peekdb/database_sqlite.rb +2 -0
- data/lib/peekdb/graph.rb +73 -0
- data/lib/peekdb.rb +97 -0
- data/peekdb.gemspec +21 -0
- data/spec/peekdb/graph_spec.rb +18 -0
- data/spec/spec_helper.rb +2 -0
- metadata +114 -0
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm --create ruby-1.9.2-p290@peekdb
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.1.3)
|
5
|
+
pg (0.13.2)
|
6
|
+
rspec (2.9.0)
|
7
|
+
rspec-core (~> 2.9.0)
|
8
|
+
rspec-expectations (~> 2.9.0)
|
9
|
+
rspec-mocks (~> 2.9.0)
|
10
|
+
rspec-core (2.9.0)
|
11
|
+
rspec-expectations (2.9.1)
|
12
|
+
diff-lcs (~> 1.1.3)
|
13
|
+
rspec-mocks (2.9.0)
|
14
|
+
ruby-graphviz (1.0.5)
|
15
|
+
shoulda (3.0.1)
|
16
|
+
shoulda-context (~> 1.0.0)
|
17
|
+
shoulda-matchers (~> 1.0.0)
|
18
|
+
shoulda-context (1.0.0)
|
19
|
+
shoulda-matchers (1.0.0)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
pg
|
26
|
+
rspec
|
27
|
+
ruby-graphviz
|
28
|
+
shoulda
|
data/README.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# PeekDB - Database Quick View
|
2
|
+
|
3
|
+
A command line utility that builds a simple force-directed ER diagram for a database. The output can be .png or .dot formats saved in the current directory. Dot files are great for reuse within Omnigraffle.
|
4
|
+
|
5
|
+
|
6
|
+
Installation
|
7
|
+
-------
|
8
|
+
gem install peek
|
9
|
+
|
10
|
+
Usage
|
11
|
+
-------
|
12
|
+
peekdb -n [database-name] -t [psql | mysql | sqlite ] -f [png | dot]
|
13
|
+
|
14
|
+
Todo
|
15
|
+
-------
|
16
|
+
* Add support for SQLite and MySQL
|
17
|
+
* Add support for extraction of views
|
18
|
+
* Add support for extraction of column names
|
19
|
+
|
20
|
+
License
|
21
|
+
-------
|
22
|
+
|
23
|
+
MIT License. Copyright 2012 Ennova.
|
data/Rakefile
ADDED
data/bin/peekdb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
class Database
|
2
|
+
|
3
|
+
attr_reader :name, :connection, :tables, :relations
|
4
|
+
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
|
8
|
+
begin
|
9
|
+
open_connection(@name)
|
10
|
+
puts "... Inspecting database #{@name}"
|
11
|
+
|
12
|
+
find_tables
|
13
|
+
puts "... Found #{@tables.size} tables"
|
14
|
+
|
15
|
+
find_relations
|
16
|
+
puts "... Found #{@relations.size} relations"
|
17
|
+
rescue Exception => e
|
18
|
+
puts "... Error #{e}"
|
19
|
+
exit(1)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def open_connection(database_name)
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_tables
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_relations
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class DatabasePSQL < Database
|
2
|
+
|
3
|
+
attr_reader :name, :connection, :tables, :relations
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def open_connection(database_name)
|
8
|
+
@connection = PGconn.open(:dbname => @name)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Reference from PostgreSQL:
|
12
|
+
# \d information_schema.tables
|
13
|
+
# Column | Type | Modifiers
|
14
|
+
# -----------------------------+-----------------------------------+-----------
|
15
|
+
# table_catalog | information_schema.sql_identifier |
|
16
|
+
# table_schema | information_schema.sql_identifier |
|
17
|
+
# table_name | information_schema.sql_identifier |
|
18
|
+
# table_type | information_schema.character_data |
|
19
|
+
# self_referencing_column_name | information_schema.sql_identifier |
|
20
|
+
# reference_generation | information_schema.character_data |
|
21
|
+
# user_defined_type_catalog | information_schema.sql_identifier |
|
22
|
+
# user_defined_type_schema | information_schema.sql_identifier |
|
23
|
+
# user_defined_type_name | information_schema.sql_identifier |
|
24
|
+
# is_insertable_into | information_schema.yes_or_no |
|
25
|
+
# is_typed | information_schema.yes_or_no |
|
26
|
+
# commit_action | information_schema.character_data |
|
27
|
+
def find_tables
|
28
|
+
sql = <<-eos
|
29
|
+
SELECT table_name
|
30
|
+
FROM information_schema.tables
|
31
|
+
WHERE table_type = 'BASE TABLE'
|
32
|
+
AND table_schema = 'public'
|
33
|
+
eos
|
34
|
+
@tables = @connection.exec(sql).values.flatten
|
35
|
+
end
|
36
|
+
|
37
|
+
# Reference from PostgreSQL:
|
38
|
+
# [0] constraint_name
|
39
|
+
# [1] table_name
|
40
|
+
# [2] column_name
|
41
|
+
# [3] foreign_table_name
|
42
|
+
# [4] foreign_column_name
|
43
|
+
def find_relations
|
44
|
+
sql = <<-eos
|
45
|
+
SELECT
|
46
|
+
tc.constraint_name, tc.table_name, kcu.column_name,
|
47
|
+
ccu.table_name AS foreign_table_name,
|
48
|
+
ccu.column_name AS foreign_column_name
|
49
|
+
FROM
|
50
|
+
information_schema.table_constraints AS tc
|
51
|
+
JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
|
52
|
+
JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
|
53
|
+
WHERE constraint_type = 'FOREIGN KEY'
|
54
|
+
eos
|
55
|
+
@relations = @connection.exec(sql).values
|
56
|
+
end
|
57
|
+
end
|
data/lib/peekdb/graph.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
class Graph
|
2
|
+
|
3
|
+
attr_reader :dwg
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@dwg = GraphViz.new(:G, :type => :digraph)
|
7
|
+
end
|
8
|
+
|
9
|
+
def build(name, tables, relations)
|
10
|
+
config_graph(name)
|
11
|
+
config_nodes
|
12
|
+
config_edges
|
13
|
+
|
14
|
+
tables.each do |table|
|
15
|
+
@dwg.add_nodes(table)
|
16
|
+
end
|
17
|
+
|
18
|
+
relations.each do |relation|
|
19
|
+
@dwg.add_edges(relation[3], relation[1])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def output(name, format)
|
24
|
+
case format
|
25
|
+
when :pdf, nil
|
26
|
+
filename = "#{name}.pdf"
|
27
|
+
@dwg.output(:pdf => "#{filename}")
|
28
|
+
puts "... Writing output #{filename}"
|
29
|
+
when :dot
|
30
|
+
filename = "#{name}.dot"
|
31
|
+
@dwg.output(:dot => filename)
|
32
|
+
puts "... Writing output #{filename}"
|
33
|
+
else
|
34
|
+
raise ArgumentError.new("Unknown output format #{format}")
|
35
|
+
exit(1)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def config_graph(name)
|
42
|
+
title = name.split('_').each{|s| s.capitalize!}.join(' ')
|
43
|
+
date = Time.now
|
44
|
+
@dwg.graph[:labelloc] = "t"
|
45
|
+
@dwg.graph[:label] = "#{title} Database\nGenerated #{date}"
|
46
|
+
@dwg.graph[:fontname] = "Helvetica"
|
47
|
+
@dwg.graph[:fontcolor] = "#666666"
|
48
|
+
@dwg.graph[:fontsize] = "24"
|
49
|
+
end
|
50
|
+
|
51
|
+
def config_nodes
|
52
|
+
@dwg.node[:color] = "#222222"
|
53
|
+
@dwg.node[:style] = "filled"
|
54
|
+
@dwg.node[:shape] = "box"
|
55
|
+
@dwg.node[:penwidth] = "1"
|
56
|
+
@dwg.node[:fontname] = "Helvetica"
|
57
|
+
@dwg.node[:fillcolor] = "#eeeeee"
|
58
|
+
@dwg.node[:fontcolor] = "#333333"
|
59
|
+
@dwg.node[:margin] = "0.05"
|
60
|
+
@dwg.node[:fontsize] = "12"
|
61
|
+
end
|
62
|
+
|
63
|
+
def config_edges
|
64
|
+
@dwg.edge[:color] = "#666666"
|
65
|
+
@dwg.edge[:weight] = "1"
|
66
|
+
@dwg.edge[:fontsize] = "10"
|
67
|
+
@dwg.edge[:fontcolor] = "#444444"
|
68
|
+
@dwg.edge[:fontname] = "Helvetica"
|
69
|
+
@dwg.edge[:dir] = "forward"
|
70
|
+
@dwg.edge[:arrowsize] = "0.5"
|
71
|
+
@dwg.edge[:arrowhead] = "crow"
|
72
|
+
end
|
73
|
+
end
|
data/lib/peekdb.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'pg'
|
2
|
+
require 'graphviz'
|
3
|
+
require 'optparse'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'pry'
|
6
|
+
|
7
|
+
require_relative 'peekdb/graph'
|
8
|
+
require_relative 'peekdb/database'
|
9
|
+
require_relative 'peekdb/database_psql'
|
10
|
+
require_relative 'peekdb/database_mysql'
|
11
|
+
require_relative 'peekdb/database_sqlite'
|
12
|
+
|
13
|
+
class PeekDB
|
14
|
+
|
15
|
+
def initialize(argv)
|
16
|
+
puts "PeekDB:"
|
17
|
+
begin
|
18
|
+
parse_options(argv)
|
19
|
+
rescue Exception => e
|
20
|
+
puts "... Error FATAL: #{e}"
|
21
|
+
exit(1)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_options(args)
|
26
|
+
@options = OpenStruct.new
|
27
|
+
|
28
|
+
opts = OptionParser.new do |opts|
|
29
|
+
opts.banner = "Usage: peekdb [options]"
|
30
|
+
opts.program_name = "PeekDB"
|
31
|
+
opts.separator ""
|
32
|
+
opts.separator "Specific options:"
|
33
|
+
|
34
|
+
opts.on("-n", "--name NAME", "Name of database - required") do |name|
|
35
|
+
@options.name = name
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on("-t", "--type [TYPE]", [:psql, :mysql, :sqlite], "Type of database (psql, mysql, sqlite) - required") do |type|
|
39
|
+
@options.type = type
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on("-f", "--format [FORMAT]", [:pdf, :dot], "Format of output file (pdf, dot) - default pdf") do |format|
|
43
|
+
@options.format = format
|
44
|
+
end
|
45
|
+
end
|
46
|
+
opts.parse!(args)
|
47
|
+
end
|
48
|
+
|
49
|
+
def run
|
50
|
+
if @options.name && @options.name
|
51
|
+
case @options.type
|
52
|
+
when :psql
|
53
|
+
db = DatabasePSQL.new @options.name
|
54
|
+
when :mysql
|
55
|
+
db = DatabaseMySQL.new @options.name
|
56
|
+
when :sqlite
|
57
|
+
db = DatabaseSQLite.new @options.name
|
58
|
+
else
|
59
|
+
puts "... Error FATAL: Unknown database type #{@options.type}"
|
60
|
+
exit(1)
|
61
|
+
end
|
62
|
+
|
63
|
+
graph = Graph.new
|
64
|
+
graph.build(db.name, db.tables, db.relations)
|
65
|
+
graph.output(db.name, @options.format)
|
66
|
+
else
|
67
|
+
puts '... Error FATAL: missing arguments'
|
68
|
+
exit(1)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.usage
|
73
|
+
puts <<-EOT
|
74
|
+
Usage: rails COMMAND [ARGS]
|
75
|
+
|
76
|
+
The most common rails commands are:
|
77
|
+
generate Generate new code (short-cut alias: "g")
|
78
|
+
console Start the Rails console (short-cut alias: "c")
|
79
|
+
server Start the Rails server (short-cut alias: "s")
|
80
|
+
dbconsole Start a console for the database specified in config/database.yml
|
81
|
+
(short-cut alias: "db")
|
82
|
+
new Create a new Rails application. "rails new my_app" creates a
|
83
|
+
new application called MyApp in "./my_app"
|
84
|
+
|
85
|
+
In addition to those, there are:
|
86
|
+
application Generate the Rails application code
|
87
|
+
destroy Undo code generated with "generate" (short-cut alias: "d")
|
88
|
+
benchmarker See how fast a piece of code runs
|
89
|
+
profiler Get profile information from a piece of code
|
90
|
+
plugin new Generates skeleton for developing a Rails plugin
|
91
|
+
runner Run a piece of code in the application environment (short-cut alias: "r")
|
92
|
+
|
93
|
+
All commands can be run with -h (or --help) for more information.
|
94
|
+
EOT
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
data/peekdb.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = "peekdb"
|
3
|
+
gem.version = "0.2.0"
|
4
|
+
gem.platform = Gem::Platform::RUBY
|
5
|
+
gem.authors = ["Adrian Smith"]
|
6
|
+
gem.email = ["adrian.smith@ennova.com.au"]
|
7
|
+
gem.homepage = "https://github.com/AdrianSmith/peekdb"
|
8
|
+
gem.summary = %q{PeekDB generates a quick view of the tables and relationships in a database}
|
9
|
+
gem.description = %q{PeekDB is a command line utility that builds a simple force-directed ER diagram for a database. The output can be .png or .dot formats.}
|
10
|
+
gem.license = 'MIT'
|
11
|
+
|
12
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
13
|
+
gem.files = `git ls-files`.split("\n")
|
14
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
|
16
|
+
gem.add_dependency 'ruby-graphviz'
|
17
|
+
gem.add_dependency 'pg'
|
18
|
+
|
19
|
+
gem.add_development_dependency 'rake'
|
20
|
+
gem.add_development_dependency 'rspec'
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative '../spec_helper.rb'
|
2
|
+
|
3
|
+
describe Graph do
|
4
|
+
before do
|
5
|
+
@tables = ['book', 'author', 'format']
|
6
|
+
@relations = [
|
7
|
+
['author-book', 'author', 'id', 'book', 'id'],
|
8
|
+
['book-format', 'book', 'id', 'format', 'id']
|
9
|
+
]
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should create a new graph using database table and relation data" do
|
13
|
+
graph = Graph.new
|
14
|
+
graph.build('test', @tables, @relations)
|
15
|
+
graph.dwg.should be_not_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: peekdb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.2.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Adrian Smith
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2012-04-29 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: ruby-graphviz
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: pg
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rake
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rspec
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
type: :development
|
58
|
+
version_requirements: *id004
|
59
|
+
description: PeekDB is a command line utility that builds a simple force-directed ER diagram for a database. The output can be .png or .dot formats.
|
60
|
+
email:
|
61
|
+
- adrian.smith@ennova.com.au
|
62
|
+
executables:
|
63
|
+
- peekdb
|
64
|
+
extensions: []
|
65
|
+
|
66
|
+
extra_rdoc_files: []
|
67
|
+
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .rvmrc
|
71
|
+
- Gemfile
|
72
|
+
- Gemfile.lock
|
73
|
+
- README.md
|
74
|
+
- Rakefile
|
75
|
+
- bin/peekdb
|
76
|
+
- lib/peekdb.rb
|
77
|
+
- lib/peekdb/database.rb
|
78
|
+
- lib/peekdb/database_mysql.rb
|
79
|
+
- lib/peekdb/database_psql.rb
|
80
|
+
- lib/peekdb/database_sqlite.rb
|
81
|
+
- lib/peekdb/graph.rb
|
82
|
+
- peekdb.gemspec
|
83
|
+
- spec/peekdb/graph_spec.rb
|
84
|
+
- spec/spec_helper.rb
|
85
|
+
homepage: https://github.com/AdrianSmith/peekdb
|
86
|
+
licenses:
|
87
|
+
- MIT
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: "0"
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: "0"
|
105
|
+
requirements: []
|
106
|
+
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 1.8.23
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: PeekDB generates a quick view of the tables and relationships in a database
|
112
|
+
test_files:
|
113
|
+
- spec/peekdb/graph_spec.rb
|
114
|
+
- spec/spec_helper.rb
|