hgimenez-peegee 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +54 -0
- data/Rakefile +50 -0
- data/VERSION.yml +4 -0
- data/lib/peegee/clustering.rb +118 -0
- data/lib/peegee/constraint.rb +28 -0
- data/lib/peegee/foreign_key.rb +19 -0
- data/lib/peegee/index.rb +72 -0
- data/lib/peegee/primary_key.rb +19 -0
- data/lib/peegee/table.rb +168 -0
- data/lib/peegee/unique_constraint.rb +19 -0
- data/lib/peegee.rb +6 -0
- data/peegee.gemspec +54 -0
- data/spec/peegee_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- metadata +76 -0
data/README.textile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
h1. What?
|
2
|
+
|
3
|
+
Peegee (pronounced "Pee - Gee", as in the letters PG, for Postgres) is a utility that provides an abstraction for performing PostgreSQL related tasks from Ruby, or your Rails application.
|
4
|
+
|
5
|
+
h1. Why?
|
6
|
+
|
7
|
+
This project started with the need to improve the performance of PostgreSQL's CLUSTER command. While clustering your database tables improves query performance significantly, the actual process of clustering takes unacceptable amounts of time. For instance, it may take roughly 20 hours on a moderately big set of tables (around 60GB). Your milage may vary, of course.
|
8
|
+
Refactoring the clustering functionality surfaced a clean set of utilities that resulted in the creation of this gem. Some are useful, and some are just pointless.
|
9
|
+
|
10
|
+
The initial code for this gem was created during the Boston.rb hackfest at Thoughtbot, on April 7th, 2009.
|
11
|
+
|
12
|
+
h1. Example usage
|
13
|
+
|
14
|
+
Assuming we're in a Rails app, you can simply do:
|
15
|
+
|
16
|
+
<pre><code>people_table = Peegee::Table.new(:table_name => 'people')
|
17
|
+
people_table.ddl # a string containing this table's DDL
|
18
|
+
people_table.indexes
|
19
|
+
people_table.foreign_keys
|
20
|
+
people_table.dependent_foreign_keys</code></pre>
|
21
|
+
The three commands above return arrays of Peegee::Index and Peegee::ForeignKey objects, which all respond to drop and create.
|
22
|
+
For example, you could drop and recreate the first dependent foreign key (some other table referencing people) with:
|
23
|
+
<pre><code>people_table.dependent_foreign_keys.first.drop
|
24
|
+
people_table.dependent_foreign_keys.first.create</code></pre>
|
25
|
+
After the call to drop, the @dependent_foreign_keys instance variable on the people_table object remains cached, and calling create will simply execute the cached SQL required to recreate the database object.
|
26
|
+
|
27
|
+
The strategy used for clustering a table is to store all dependencies in order to restore them later (as viewed above), and move all of the table's data on the order given by a certain index. This is all executed in a database transaction in case it blows up. The result should be just like the pgsql native CLUSTER command:
|
28
|
+
<pre><code>people_table.cluster('people_pk') #must specify an index by which to cluster</code></pre>
|
29
|
+
|
30
|
+
h2. Todo
|
31
|
+
|
32
|
+
The first order of business will be to create specs where applicable.
|
33
|
+
|
34
|
+
Then, the idea is to keep adding functionality that may be useful for DBAs or application developers and other PostgreSQL users. Some that come to mind are:
|
35
|
+
|
36
|
+
* Ability to clean out all of table's index and foreign key names, following a given pattern. On occasions, table names are altered leaving behind pesky legacy names for related objects.
|
37
|
+
* Ability to identify database stinks. For instance:
|
38
|
+
** tables without primary keys, or indexes (which are not pure join tables)
|
39
|
+
** foreign keys without indexes (useful for manually identifying and fixing if applicable),
|
40
|
+
** report on columns that look like foreign keys, but aren't (based on rails conventions).
|
41
|
+
** columns that are nullable, and are flagged as unique
|
42
|
+
** tables with incrementing columns names, indiciating a possible denormalization
|
43
|
+
** columns with a default value of 'NULL' (the varchar), where NULL may have been intended.
|
44
|
+
(much of these are stolen from the excellent "SchemaSpy":http://schemaspy.sourceforge.net/)
|
45
|
+
|
46
|
+
There's also a bunch of #TODO tags in the code, where there's clearly room for improvement.
|
47
|
+
|
48
|
+
I would also like to make this code ORM agnostic. Right now, it depends on ActiveRecord to retrieve a connection and execute commands.
|
49
|
+
|
50
|
+
Please fork away and help improve it.
|
51
|
+
|
52
|
+
h1. License
|
53
|
+
|
54
|
+
Copyright (c) 2009 Harold A. Gimenez, released under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "peegee"
|
8
|
+
gem.summary = %Q{A set of utilities for doing PostgreSQL database related stuffs from ruby.}
|
9
|
+
gem.email = "harold.gimenez@gmail.com"
|
10
|
+
gem.homepage = "http://github.com/hgimenez/peegee"
|
11
|
+
gem.authors = ["Harold A. Gimenez"]
|
12
|
+
gem.add_dependency 'activerecord'
|
13
|
+
gem.requirements << 'A functioning PostgreSQL database, configured via ActiveRecord (for example, database.yml on a Rails project, or inline within your scripts).'
|
14
|
+
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
+
spec.libs << 'lib' << 'spec'
|
24
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
spec.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
task :default => :spec
|
35
|
+
|
36
|
+
require 'rake/rdoctask'
|
37
|
+
Rake::RDocTask.new do |rdoc|
|
38
|
+
if File.exist?('VERSION.yml')
|
39
|
+
config = YAML.load(File.read('VERSION.yml'))
|
40
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
41
|
+
else
|
42
|
+
version = ""
|
43
|
+
end
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "peegee #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
50
|
+
|
data/VERSION.yml
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
module Peegee
|
2
|
+
module Clustering
|
3
|
+
|
4
|
+
# Clusters this table. See http://www.postgresql.org/docs/8.3/interactive/sql-cluster.html
|
5
|
+
# Optionally specify the index to cluster by (by name or Peegee::Index object).
|
6
|
+
# If the index is not specify, it will try to induce which index to
|
7
|
+
# use by looking at postgresql's internals. If it can't find out,
|
8
|
+
# an exception will be raised.
|
9
|
+
def cluster(cluster_index = nil)
|
10
|
+
cluster_index = find_cluster_index(cluster_index)
|
11
|
+
puts "Cluster index is: #{cluster_index.inspect}"
|
12
|
+
puts "Cluster index type is: #{cluster_index.class.name}"
|
13
|
+
dependencies = remember_dependencies
|
14
|
+
ActiveRecord::Base.transaction do
|
15
|
+
puts "Clustering #{@table_name} by #{cluster_index.index_name} (with order => #{cluster_index.order})"
|
16
|
+
dependencies.map { |dep| dep.drop }
|
17
|
+
create_tmp_table
|
18
|
+
move_data(cluster_index)
|
19
|
+
drop_original_and_rename_tmp_table
|
20
|
+
dependencies.reverse.map { |dep| dep.create }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Retrieves the Peegee::Index object that will be used
|
27
|
+
# for clustering this table. The cluster_index parameter can
|
28
|
+
# be either a string representing the index name, or a Peegee::Index object.
|
29
|
+
# Raises an exception if either a proper index was not found
|
30
|
+
# or more than one index was found given a cluster_index name.
|
31
|
+
def find_cluster_index(cluster_index)
|
32
|
+
return cluster_index if cluster_index.kind_of?(Peegee::Index)
|
33
|
+
the_index = nil
|
34
|
+
if cluster_index #cluster_index is specified
|
35
|
+
the_index = indexes.select { |i| i.index_name == cluster_index}
|
36
|
+
else #find cluster index from postgresql's information schema
|
37
|
+
the_index = indexes.select { |i| i.is_clustered? }
|
38
|
+
end
|
39
|
+
raise "Ambiguous cluster_index definition. #{the_index.inspect}" if the_index.size != 1
|
40
|
+
the_index[0] #this is surely an array of size one.
|
41
|
+
end
|
42
|
+
|
43
|
+
# Creates the tmp table to be used to temporarily hold
|
44
|
+
# the data of the table being clustered. Additionally,
|
45
|
+
# it assigns the sequence of the table to the tmp table.
|
46
|
+
def create_tmp_table
|
47
|
+
ActiveRecord::Base.connection.execute(tmp_ddl)
|
48
|
+
reasign_sequence_to_tmp_table
|
49
|
+
end
|
50
|
+
|
51
|
+
# Moves the data contained in this table
|
52
|
+
# to a tmp table in the order specified by
|
53
|
+
# its cluster_index.
|
54
|
+
def move_data(cluster_index)
|
55
|
+
ActiveRecord::Base.connection.execute("insert into #{@table_name}_tmp select * from #{@table_name} order by #{cluster_index.order}")
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# Returns the DDL for the tmp table where data is moved
|
60
|
+
# to temporarily during clustering.
|
61
|
+
def tmp_ddl
|
62
|
+
ddl.gsub(/\b#{@table_name}\b/, "#{@table_name}_tmp")
|
63
|
+
end
|
64
|
+
|
65
|
+
# Alters the sequence of the table being clustered,
|
66
|
+
# by assigning it to its temp table. This is necessary in order to
|
67
|
+
# drop the table being clustered (to clean out the dependency on
|
68
|
+
# the sequence).
|
69
|
+
# This method is called after having created the tmp table, otherwise it will fail.
|
70
|
+
#
|
71
|
+
# The method for finding a table's sequence is hackish.
|
72
|
+
# TODO: Find a better method for finding a table's sequence.
|
73
|
+
def reasign_sequence_to_tmp_table
|
74
|
+
sql = 'SELECT a.attname, ' +
|
75
|
+
'pg_catalog.format_type(a.atttypid, a.atttypmod), ' +
|
76
|
+
'(SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for 128) ' +
|
77
|
+
' FROM pg_catalog.pg_attrdef d ' +
|
78
|
+
' WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), ' +
|
79
|
+
'a.attnotnull, a.attnum ' +
|
80
|
+
'FROM pg_catalog.pg_attribute a ' +
|
81
|
+
"WHERE a.attrelid = '#{oid}' AND a.attnum > 0 AND NOT a.attisdropped " +
|
82
|
+
" and " +
|
83
|
+
'(SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for 128) ' +
|
84
|
+
' FROM pg_catalog.pg_attrdef d ' +
|
85
|
+
" WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef) like '%nextval%' " +
|
86
|
+
'ORDER BY a.attnum '
|
87
|
+
seq = ActiveRecord::Base.connection.execute(sql).entries.flatten
|
88
|
+
if !seq[0].nil? and !seq[2].empty?
|
89
|
+
seq[2].match /nextval\('[\"']{0,2}(\w*)[\"']{0,2}.*'::regclass\)/
|
90
|
+
sequence_name = $1 #the match above...
|
91
|
+
ActiveRecord::Base.connection.execute("ALTER SEQUENCE \"#{sequence_name}\" OWNED BY #{@table_name}_tmp.#{seq[0]}")
|
92
|
+
else
|
93
|
+
puts "\n\n\t*****#{@table_name} apparently doesn't have a sequence"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
# Drops the table being clustered,
|
99
|
+
# and renames the tmp table to its original name.
|
100
|
+
def drop_original_and_rename_tmp_table
|
101
|
+
sql = []
|
102
|
+
sql << "drop table #{@table_name};"
|
103
|
+
sql << "alter table #{@table_name}_tmp rename to #{@table_name};"
|
104
|
+
sql.each do |s|
|
105
|
+
ActiveRecord::Base.connection.execute(s + ';')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Retreives an array of this table's foreign keys,
|
110
|
+
# dependent foreign keys, and indexes.
|
111
|
+
def remember_dependencies
|
112
|
+
dependencies = []
|
113
|
+
dependencies << foreign_keys << dependent_foreign_keys << indexes
|
114
|
+
dependencies.flatten
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Peegee
|
2
|
+
class Constraint
|
3
|
+
|
4
|
+
def initialize(opts = {})
|
5
|
+
@table_name = opts[:table_name]
|
6
|
+
@constraint_name = opts[:constraint_name]
|
7
|
+
@constraint_def = opts[:constraint_def]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns human readable constraint.
|
11
|
+
def to_s
|
12
|
+
"Constraint: #{@constraint_name} on #{table_name}"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Creates this constraint definition.
|
16
|
+
def create
|
17
|
+
sql = "alter table #{@table_name} add constraint #{@constraint_name} #{@constraint_def}"
|
18
|
+
ActiveRecord::Base.connection.execute(sql)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Drops this constraint.
|
22
|
+
def drop
|
23
|
+
sql = "alter table #{@table_name} drop constraint #{@constraint_name}"
|
24
|
+
ActiveRecord::Base.connection.execute(sql)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Peegee
|
2
|
+
class ForeignKey < Peegee::Constraint
|
3
|
+
|
4
|
+
def initialize(opts)
|
5
|
+
super(opts)
|
6
|
+
end
|
7
|
+
|
8
|
+
# Returns the name of this foreign key.
|
9
|
+
def foreign_key_name
|
10
|
+
@constraint_name
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns human readable foreign key definition
|
14
|
+
def to_s
|
15
|
+
"Foreign Key: #{foreign_key_name} on #{table_name}"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
data/lib/peegee/index.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
module Peegee
|
2
|
+
class Index
|
3
|
+
|
4
|
+
attr_accessor :index_name
|
5
|
+
|
6
|
+
def initialize(opts = {})
|
7
|
+
@table_name = opts[:table_name]
|
8
|
+
@index_name = opts[:index_name]
|
9
|
+
@clustered = opts[:clustered]
|
10
|
+
@def = opts[:def]
|
11
|
+
end
|
12
|
+
|
13
|
+
# prints human readable index definition
|
14
|
+
def to_s
|
15
|
+
"Index: #{@index_name} on #{@table_name} => (#{columns})"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the columns affected by this index
|
19
|
+
# TODO: Find out if there's a better way to
|
20
|
+
# retreive the column list, via postgres internals.
|
21
|
+
#
|
22
|
+
# TODO: Find out if this is the cleanest way to get
|
23
|
+
# the match of the regex
|
24
|
+
def columns
|
25
|
+
raise "Can't find index order for #{@index_name}" unless @def.match /\((.*)\)/
|
26
|
+
$1 #return the regex match (column list)
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_method :order, :columns
|
30
|
+
|
31
|
+
# Drops this index
|
32
|
+
def drop
|
33
|
+
ActiveRecord::Base.connection.execute(drop_statement)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Constructs the SQL statement to drop this index
|
37
|
+
# Optionally, pass in the boolean cascade value to
|
38
|
+
# also drop any dependent DB objects.
|
39
|
+
#
|
40
|
+
# PostgreSQL will error out if the index does not exists
|
41
|
+
# We purposely do not work around that.
|
42
|
+
def drop_statement(cascade = false)
|
43
|
+
drop_statement = "DROP INDEX \"#{@index_name}\""
|
44
|
+
drop_statement += " CASCADE" if cascade
|
45
|
+
drop_statement
|
46
|
+
end
|
47
|
+
|
48
|
+
# Creates this index
|
49
|
+
def create
|
50
|
+
ActiveRecord::Base.connection.execute(@def)
|
51
|
+
end
|
52
|
+
|
53
|
+
# returns true if this was the last clustered index
|
54
|
+
# on the associated table
|
55
|
+
def is_clustered?
|
56
|
+
@clustered
|
57
|
+
end
|
58
|
+
|
59
|
+
# Builds and returns Index object, given the index name
|
60
|
+
# The optional table_name is required when
|
61
|
+
# the index_name is not unique, ie: More than one
|
62
|
+
# index with that name was found on PostgreSQL's
|
63
|
+
# information catalog.
|
64
|
+
# If more than one index by that name is found,
|
65
|
+
# and table_name is not specified, an exception is raised.
|
66
|
+
# TODO: write implementation of this method...
|
67
|
+
def self.build(index_name, table_name = nil)
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Peegee
|
2
|
+
class PrimaryKey < Peegee::Constraint
|
3
|
+
|
4
|
+
def initialize(opts)
|
5
|
+
super(opts)
|
6
|
+
end
|
7
|
+
|
8
|
+
# Returns the name of this primary key.
|
9
|
+
def primary_key_name
|
10
|
+
@constraint_name
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns human readable primary key definition
|
14
|
+
def to_s
|
15
|
+
"Primary Key: #{primary_key_name} on #{@table_name}"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
data/lib/peegee/table.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/clustering'
|
2
|
+
module Peegee
|
3
|
+
|
4
|
+
class Table
|
5
|
+
include Peegee::Clustering
|
6
|
+
|
7
|
+
def initialize(opts = {})
|
8
|
+
@table_name = opts[:table_name]
|
9
|
+
end
|
10
|
+
|
11
|
+
def oid
|
12
|
+
@oid ||= fetch_oid
|
13
|
+
end
|
14
|
+
|
15
|
+
def foreign_keys
|
16
|
+
@foreign_keys ||= fetch_foreign_keys
|
17
|
+
end
|
18
|
+
|
19
|
+
def dependent_foreign_keys
|
20
|
+
@dependent_foreign_keys ||= fetch_dependent_foreign_keys
|
21
|
+
end
|
22
|
+
|
23
|
+
def primary_keys
|
24
|
+
@primary_keys ||= fetch_primary_keys
|
25
|
+
end
|
26
|
+
|
27
|
+
def unique_constraints
|
28
|
+
@unique_constraints ||= fetch_unique_constraints
|
29
|
+
end
|
30
|
+
|
31
|
+
def indexes
|
32
|
+
@indexes ||= fetch_indexes
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the DDL for this table as a string.
|
36
|
+
def ddl
|
37
|
+
sql = 'SELECT a.attname, ' +
|
38
|
+
'pg_catalog.format_type(a.atttypid, a.atttypmod), ' +
|
39
|
+
'(SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for 128) ' +
|
40
|
+
' FROM pg_catalog.pg_attrdef d ' +
|
41
|
+
' WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef), ' +
|
42
|
+
'a.attnotnull, a.attnum ' +
|
43
|
+
'FROM pg_catalog.pg_attribute a ' +
|
44
|
+
"WHERE a.attrelid = '#{oid}' AND a.attnum > 0 AND NOT a.attisdropped " +
|
45
|
+
'ORDER BY a.attnum '
|
46
|
+
|
47
|
+
column_defs = ActiveRecord::Base.connection.execute(sql).entries
|
48
|
+
|
49
|
+
ddl = "create table #{@table_name} ( "
|
50
|
+
columns = []
|
51
|
+
column_defs.each do |column_def|
|
52
|
+
#name and type
|
53
|
+
column = column_def[0] + ' ' + column_def[1] + ' '
|
54
|
+
#not null?
|
55
|
+
column += 'NOT NULL ' if column_def[3] == 't'
|
56
|
+
#add modifiers:
|
57
|
+
column += 'DEFAULT ' + column_def[2] unless (column_def[2].nil? || column_def[2].empty?)
|
58
|
+
columns << column
|
59
|
+
end
|
60
|
+
|
61
|
+
ddl += columns.join(', ') + ' )'
|
62
|
+
ddl.gsub('\\','')
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
private
|
67
|
+
def fetch_oid
|
68
|
+
sql = 'SELECT c.oid ' +
|
69
|
+
' FROM pg_catalog.pg_class c ' +
|
70
|
+
' LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace ' +
|
71
|
+
" WHERE c.relname ~ '^(#{@table_name})$' " +
|
72
|
+
' AND pg_catalog.pg_table_is_visible(c.oid) '
|
73
|
+
return ActiveRecord::Base.connection.execute(sql).entries[0]
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
def fetch_primary_keys
|
79
|
+
sql = 'select conname ' +
|
80
|
+
' , pg_get_constraintdef(pk.oid, true) as foreign_key ' +
|
81
|
+
' from pg_catalog.pg_constraint pk ' +
|
82
|
+
' inner join pg_class c ' +
|
83
|
+
' on c.oid = pk.conrelid ' +
|
84
|
+
" where contype = 'p' " +
|
85
|
+
" and c.oid = '#{@table_name}'::regclass "
|
86
|
+
raw_pks = ActiveRecord::Base.connection.execute(sql).entries
|
87
|
+
return raw_pks.collect do |pk|
|
88
|
+
Peegee::ForeignKey.new(#TODO: Consider passing table object (self)
|
89
|
+
:table_name => @table_name,
|
90
|
+
:constraint_name => pk[0],
|
91
|
+
:constraint_def => pk[1])
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
def fetch_unique_constraints
|
97
|
+
sql = 'select conname ' +
|
98
|
+
' , pg_get_constraintdef(uc.oid, true) as foreign_key ' +
|
99
|
+
' from pg_catalog.pg_constraint uc ' +
|
100
|
+
' inner join pg_class c ' +
|
101
|
+
' on c.oid = uc.conrelid ' +
|
102
|
+
" where contype = 'u' " +
|
103
|
+
" and c.oid = '#{@table_name}'::regclass "
|
104
|
+
raw_ucs = ActiveRecord::Base.connection.execute(sql).entries
|
105
|
+
return raw_ucs.collect do |uc|
|
106
|
+
Peegee::UniqueConstraint.new(#TODO: Consider passing table object (self)
|
107
|
+
:table_name => @table_name,
|
108
|
+
:constraint_name => uc[0],
|
109
|
+
:constraint_def => uc[1])
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def fetch_foreign_keys
|
114
|
+
sql = 'select conname ' +
|
115
|
+
' , pg_get_constraintdef(fk.oid, true) as foreign_key ' +
|
116
|
+
' from pg_catalog.pg_constraint fk ' +
|
117
|
+
' inner join pg_class c ' +
|
118
|
+
' on c.oid = fk.conrelid ' +
|
119
|
+
" where contype = 'f' " +
|
120
|
+
" and c.oid = '#{@table_name}'::regclass "
|
121
|
+
raw_fks = ActiveRecord::Base.connection.execute(sql).entries
|
122
|
+
return raw_fks.collect do |fk|
|
123
|
+
Peegee::ForeignKey.new(#TODO: Consider passing table object (self)
|
124
|
+
:table_name => @table_name,
|
125
|
+
:constraint_name => fk[0],
|
126
|
+
:constraint_def => fk[1])
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
def fetch_dependent_foreign_keys
|
132
|
+
sql = 'select pg_constraint.conname,' +
|
133
|
+
'dep_table.relname, ' +
|
134
|
+
'pg_get_constraintdef(pg_constraint.oid, true) ' +
|
135
|
+
'from pg_constraint ' +
|
136
|
+
'inner join pg_class dep_table on pg_constraint.conrelid = dep_table.oid ' +
|
137
|
+
"where pg_constraint.confrelid = '#{@table_name}'::regclass; "
|
138
|
+
raw_dpfks = ActiveRecord::Base.connection.execute(sql).entries
|
139
|
+
return raw_dpfks.collect do |dpfk|
|
140
|
+
Peegee::ForeignKey.new(#TODO: Consider passing table object (self)
|
141
|
+
:table_name => dpfk[1],
|
142
|
+
:constraint_name => dpfk[0],
|
143
|
+
:constraint_def => dpfk[2])
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def fetch_indexes
|
148
|
+
sql = "SELECT pg_get_indexdef(i.indexrelid, 0, true) as index_definition " +
|
149
|
+
", c2.relname as index_name " +
|
150
|
+
", i.indisclustered " +
|
151
|
+
"FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i " +
|
152
|
+
"WHERE c.oid = '#{@table_name}'::regclass AND c.oid = i.indrelid AND i.indexrelid = c2.oid " #+
|
153
|
+
#"AND i.indisprimary = false" #don't get primary key constraints...
|
154
|
+
#indexes = ActiveRecord::Base.connection.execute(sql).entries.collect{ |i| i[0] + ';'}
|
155
|
+
indexes = ActiveRecord::Base.connection.execute(sql).entries
|
156
|
+
return indexes.collect do |i|
|
157
|
+
Peegee::Index.new(:table_name => @table_name,
|
158
|
+
:index_name => i[1],
|
159
|
+
:clustered => (i[2] == 't' ? true : false),
|
160
|
+
:def => i[0])
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Peegee
|
2
|
+
class UniqueConstraint
|
3
|
+
|
4
|
+
def initialize(opts)
|
5
|
+
super(opts)
|
6
|
+
end
|
7
|
+
|
8
|
+
# Returns the name of this unique constraint
|
9
|
+
def unique_constraint_name
|
10
|
+
@constraint_name
|
11
|
+
end
|
12
|
+
|
13
|
+
#returns human readable unique constraint definition
|
14
|
+
def to_s
|
15
|
+
"Unique Constraint: #{unique_constraint_name} on #{table_name}"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
data/lib/peegee.rb
ADDED
data/peegee.gemspec
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{peegee}
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Harold A. Gimenez"]
|
9
|
+
s.date = %q{2009-04-10}
|
10
|
+
s.email = %q{harold.gimenez@gmail.com}
|
11
|
+
s.extra_rdoc_files = [
|
12
|
+
"README.textile"
|
13
|
+
]
|
14
|
+
s.files = [
|
15
|
+
"README.textile",
|
16
|
+
"Rakefile",
|
17
|
+
"VERSION.yml",
|
18
|
+
"lib/peegee.rb",
|
19
|
+
"lib/peegee/clustering.rb",
|
20
|
+
"lib/peegee/constraint.rb",
|
21
|
+
"lib/peegee/foreign_key.rb",
|
22
|
+
"lib/peegee/index.rb",
|
23
|
+
"lib/peegee/primary_key.rb",
|
24
|
+
"lib/peegee/table.rb",
|
25
|
+
"lib/peegee/unique_constraint.rb",
|
26
|
+
"peegee.gemspec",
|
27
|
+
"spec/peegee_spec.rb",
|
28
|
+
"spec/spec_helper.rb"
|
29
|
+
]
|
30
|
+
s.has_rdoc = true
|
31
|
+
s.homepage = %q{http://github.com/hgimenez/peegee}
|
32
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
33
|
+
s.require_paths = ["lib"]
|
34
|
+
s.requirements = ["A functioning PostgreSQL database, configured via ActiveRecord (for example, database.yml on a Rails project, or inline within your scripts)."]
|
35
|
+
s.rubygems_version = %q{1.3.1}
|
36
|
+
s.summary = %q{A set of utilities for doing PostgreSQL database related stuffs from ruby.}
|
37
|
+
s.test_files = [
|
38
|
+
"spec/peegee_spec.rb",
|
39
|
+
"spec/spec_helper.rb"
|
40
|
+
]
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
44
|
+
s.specification_version = 2
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
47
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 0"])
|
48
|
+
else
|
49
|
+
s.add_dependency(%q<activerecord>, [">= 0"])
|
50
|
+
end
|
51
|
+
else
|
52
|
+
s.add_dependency(%q<activerecord>, [">= 0"])
|
53
|
+
end
|
54
|
+
end
|
data/spec/peegee_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hgimenez-peegee
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Harold A. Gimenez
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-10 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activerecord
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: harold.gimenez@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.textile
|
33
|
+
files:
|
34
|
+
- README.textile
|
35
|
+
- Rakefile
|
36
|
+
- VERSION.yml
|
37
|
+
- lib/peegee.rb
|
38
|
+
- lib/peegee/clustering.rb
|
39
|
+
- lib/peegee/constraint.rb
|
40
|
+
- lib/peegee/foreign_key.rb
|
41
|
+
- lib/peegee/index.rb
|
42
|
+
- lib/peegee/primary_key.rb
|
43
|
+
- lib/peegee/table.rb
|
44
|
+
- lib/peegee/unique_constraint.rb
|
45
|
+
- peegee.gemspec
|
46
|
+
- spec/peegee_spec.rb
|
47
|
+
- spec/spec_helper.rb
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://github.com/hgimenez/peegee
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options:
|
52
|
+
- --charset=UTF-8
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
requirements:
|
68
|
+
- A functioning PostgreSQL database, configured via ActiveRecord (for example, database.yml on a Rails project, or inline within your scripts).
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.2.0
|
71
|
+
signing_key:
|
72
|
+
specification_version: 2
|
73
|
+
summary: A set of utilities for doing PostgreSQL database related stuffs from ruby.
|
74
|
+
test_files:
|
75
|
+
- spec/peegee_spec.rb
|
76
|
+
- spec/spec_helper.rb
|