pg_examiner 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -13
- data/Rakefile +5 -0
- data/lib/pg_examiner.rb +2 -0
- data/lib/pg_examiner/base.rb +16 -14
- data/lib/pg_examiner/result.rb +9 -2
- data/lib/pg_examiner/result/column.rb +12 -2
- data/lib/pg_examiner/result/constraint.rb +6 -1
- data/lib/pg_examiner/result/extension.rb +9 -2
- data/lib/pg_examiner/result/function.rb +12 -2
- data/lib/pg_examiner/result/index.rb +11 -2
- data/lib/pg_examiner/result/item.rb +2 -0
- data/lib/pg_examiner/result/language.rb +5 -1
- data/lib/pg_examiner/result/schema.rb +13 -1
- data/lib/pg_examiner/result/sequence.rb +15 -0
- data/lib/pg_examiner/result/table.rb +14 -3
- data/lib/pg_examiner/result/trigger.rb +9 -2
- data/lib/pg_examiner/version.rb +3 -1
- data/pg_examiner.gemspec +3 -0
- data/spec/constraint_spec.rb +14 -12
- data/spec/extension_spec.rb +4 -2
- data/spec/function_spec.rb +7 -218
- data/spec/index_spec.rb +133 -12
- data/spec/language_spec.rb +2 -0
- data/spec/schema_spec.rb +14 -12
- data/spec/sequence_spec.rb +41 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/table_spec.rb +16 -16
- data/spec/trigger_spec.rb +310 -0
- metadata +36 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5643afd2534a1e85ecb2508240f121250c781287
|
4
|
+
data.tar.gz: cc9c1a56805634ea8d72b0ab2225b3d48fae0c21
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b7898f913fef164ab2b0394efb15890a3bfcd3bd2c7f56cdc588a7ae1e8b18445c90eb0d548ea71c95b410b4b0041435bd864ca53b59a81d294c12d06658884
|
7
|
+
data.tar.gz: 651a2956ad12e1355875dee49a59cbaa75076ce6f087ff11fddcb316f74a753bdf9bfc35075fba7e8a9017069b763298dc2e8c329b2f3371ff0f92c69ea306f2
|
data/README.md
CHANGED
@@ -2,22 +2,21 @@
|
|
2
2
|
|
3
3
|
A tool for comparing PG database structures. Use it to ensure that downward migrations precisely undo upward ones, or that different sets of migrations produce the same schema, or that different schemas in a multitenanted database all have the same structure.
|
4
4
|
|
5
|
-
PGExaminer tries to be sensible about equivalency. For example, it will understand that two tables are equivalent if they have the same name, column names
|
5
|
+
PGExaminer tries to be sensible about equivalency. For example, it will understand that two tables are equivalent if they have the same name, column names/types, triggers, constraints, and indices. It won't care about the contents of the tables, or the order in which the columns were declared.
|
6
6
|
|
7
7
|
PGExaminer is NOT exhaustive. It currently doesn't have tests for its understanding of:
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
12. ...Probably some other stuff.
|
9
|
+
* Aggregate functions.
|
10
|
+
* Column TOAST settings.
|
11
|
+
* Sequences.
|
12
|
+
* Views.
|
13
|
+
* User-defined types or enums.
|
14
|
+
* Object COMMENTs.
|
15
|
+
* Materialized views.
|
16
|
+
* Exclusion constraints.
|
17
|
+
* Inheritance structures.
|
18
|
+
* User-defined objects in the system (pg_*) schemas or information_schema.
|
19
|
+
* ...Probably some other stuff.
|
21
20
|
|
22
21
|
It may or may not understand these objects. If you're using one of these, or another Postgres feature that may be considered obscure, please test it out first. I'll be happy to add support for more objects if there's demand.
|
23
22
|
|
data/Rakefile
CHANGED
data/lib/pg_examiner.rb
CHANGED
data/lib/pg_examiner/base.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PGExaminer
|
2
4
|
class Base
|
3
|
-
def
|
4
|
-
|
5
|
+
def diffable_attrs
|
6
|
+
{}
|
5
7
|
end
|
6
8
|
|
7
|
-
def
|
9
|
+
def diffable_lists
|
8
10
|
[]
|
9
11
|
end
|
10
12
|
|
@@ -17,25 +19,25 @@ module PGExaminer
|
|
17
19
|
|
18
20
|
r = {}
|
19
21
|
|
20
|
-
diffable_attrs.each do |attr|
|
21
|
-
this = @row.fetch(attr
|
22
|
-
that = other.row.fetch(attr
|
22
|
+
diffable_attrs.each do |attr, description|
|
23
|
+
this = @row.fetch(attr)
|
24
|
+
that = other.row.fetch(attr)
|
23
25
|
|
24
26
|
unless this == that
|
25
|
-
r[
|
27
|
+
r[description] = {this => that}
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
29
|
-
diffable_methods.each do |attr|
|
31
|
+
diffable_methods.each do |attr, description|
|
30
32
|
this = send(attr)
|
31
33
|
that = other.send(attr)
|
32
34
|
|
33
35
|
unless this == that
|
34
|
-
r[
|
36
|
+
r[description] = {this => that}
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
38
|
-
diffable_lists.each do |attr|
|
40
|
+
diffable_lists.each do |attr, description|
|
39
41
|
these = send(attr)
|
40
42
|
those = other.send(attr)
|
41
43
|
these_names = these.map(&:name)
|
@@ -49,16 +51,16 @@ module PGExaminer
|
|
49
51
|
end
|
50
52
|
|
51
53
|
if result.any?
|
52
|
-
r[
|
54
|
+
r[description] = result
|
53
55
|
end
|
54
56
|
else
|
55
57
|
added = those_names - these_names
|
56
58
|
removed = these_names - those_names
|
57
59
|
|
58
60
|
h = {}
|
59
|
-
h[
|
60
|
-
h[
|
61
|
-
r[
|
61
|
+
h['added'] = added if added.any?
|
62
|
+
h['removed'] = removed if removed.any?
|
63
|
+
r[description] = h
|
62
64
|
end
|
63
65
|
end
|
64
66
|
|
data/lib/pg_examiner/result.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'pg_examiner/base'
|
2
4
|
|
3
5
|
module PGExaminer
|
@@ -20,7 +22,11 @@ module PGExaminer
|
|
20
22
|
end
|
21
23
|
|
22
24
|
def diffable_lists
|
23
|
-
|
25
|
+
{
|
26
|
+
"schemas" => "schemas",
|
27
|
+
"extensions" => "extensions",
|
28
|
+
"languages" => "languages",
|
29
|
+
}
|
24
30
|
end
|
25
31
|
|
26
32
|
def schemas
|
@@ -96,7 +102,7 @@ module PGExaminer
|
|
96
102
|
SELECT oid, tgname AS name, tgrelid, tgtype, tgfoid
|
97
103
|
FROM pg_trigger
|
98
104
|
WHERE tgrelid IN (?)
|
99
|
-
AND
|
105
|
+
AND NOT tgisinternal -- Ignore foreign key and unique index triggers, which have unpredictable names.
|
100
106
|
SQL
|
101
107
|
|
102
108
|
@pg_attrdef = execute <<-SQL
|
@@ -140,5 +146,6 @@ require 'pg_examiner/result/function'
|
|
140
146
|
require 'pg_examiner/result/index'
|
141
147
|
require 'pg_examiner/result/language'
|
142
148
|
require 'pg_examiner/result/schema'
|
149
|
+
require 'pg_examiner/result/sequence'
|
143
150
|
require 'pg_examiner/result/table'
|
144
151
|
require 'pg_examiner/result/trigger'
|
@@ -1,12 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PGExaminer
|
2
4
|
class Result
|
3
5
|
class Column < Item
|
4
6
|
def diffable_methods
|
5
|
-
|
7
|
+
{
|
8
|
+
"type" => "type",
|
9
|
+
"default" => "default"
|
10
|
+
}
|
6
11
|
end
|
7
12
|
|
8
13
|
def diffable_attrs
|
9
|
-
|
14
|
+
{
|
15
|
+
'name' => "name",
|
16
|
+
'attndims' => "array dimensionality",
|
17
|
+
'attnotnull' => "column is marked not-null",
|
18
|
+
'atttypmod' => "datatype information (atttypmod)",
|
19
|
+
}
|
10
20
|
end
|
11
21
|
|
12
22
|
def type
|
@@ -1,12 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PGExaminer
|
2
4
|
class Result
|
3
5
|
class Extension < Item
|
4
6
|
def diffable_attrs
|
5
|
-
|
7
|
+
{
|
8
|
+
"name" => "name",
|
9
|
+
"extversion" => "extension version",
|
10
|
+
}
|
6
11
|
end
|
7
12
|
|
8
13
|
def diffable_methods
|
9
|
-
|
14
|
+
{
|
15
|
+
"schema_name" => "schema name"
|
16
|
+
}
|
10
17
|
end
|
11
18
|
|
12
19
|
def schema
|
@@ -1,12 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PGExaminer
|
2
4
|
class Result
|
3
5
|
class Function < Item
|
4
6
|
def diffable_attrs
|
5
|
-
|
7
|
+
{
|
8
|
+
"name" => "name",
|
9
|
+
"proargmodes" => "argument modes",
|
10
|
+
"definition" => "function definition",
|
11
|
+
}
|
6
12
|
end
|
7
13
|
|
8
14
|
def diffable_methods
|
9
|
-
|
15
|
+
{
|
16
|
+
"argument_types" => "argument types",
|
17
|
+
"return_type" => "return type",
|
18
|
+
"language" => "language",
|
19
|
+
}
|
10
20
|
end
|
11
21
|
|
12
22
|
def argument_types
|
@@ -1,12 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PGExaminer
|
2
4
|
class Result
|
3
5
|
class Index < Item
|
4
6
|
def diffable_attrs
|
5
|
-
|
7
|
+
{
|
8
|
+
"name" => "name",
|
9
|
+
"filter" => "filter expression",
|
10
|
+
"indisunique" => "index is unique",
|
11
|
+
"indisprimary" => "index is primary key",
|
12
|
+
}
|
6
13
|
end
|
7
14
|
|
8
15
|
def diffable_methods
|
9
|
-
|
16
|
+
{
|
17
|
+
"expression" => "expression"
|
18
|
+
}
|
10
19
|
end
|
11
20
|
|
12
21
|
def expression
|
@@ -1,8 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PGExaminer
|
2
4
|
class Result
|
3
5
|
class Schema < Item
|
4
6
|
def diffable_lists
|
5
|
-
|
7
|
+
{
|
8
|
+
"tables" => "tables",
|
9
|
+
"sequences" => "sequences",
|
10
|
+
"functions" => "functions",
|
11
|
+
}
|
6
12
|
end
|
7
13
|
|
8
14
|
def tables
|
@@ -11,6 +17,12 @@ module PGExaminer
|
|
11
17
|
end.map{|row| Table.new(result, row, self)}.sort_by(&:name)
|
12
18
|
end
|
13
19
|
|
20
|
+
def sequences
|
21
|
+
@sequences ||= result.pg_class.select do |c|
|
22
|
+
c['relnamespace'] == oid && c['relkind'] == 'S'
|
23
|
+
end.map{|row| Sequence.new(result, row, self)}.sort_by(&:name)
|
24
|
+
end
|
25
|
+
|
14
26
|
def functions
|
15
27
|
@functions ||= result.pg_proc.select do |c|
|
16
28
|
c['pronamespace'] == oid
|
@@ -1,18 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PGExaminer
|
2
4
|
class Result
|
3
5
|
class Table < Item
|
4
6
|
def diffable_lists
|
5
|
-
|
7
|
+
{
|
8
|
+
"columns" => "columns",
|
9
|
+
"indexes" => "indexes",
|
10
|
+
"constraints" => "constraints",
|
11
|
+
"triggers" => "triggers",
|
12
|
+
}
|
6
13
|
end
|
7
14
|
|
8
15
|
def diffable_attrs
|
9
|
-
|
16
|
+
{
|
17
|
+
"name" => "name",
|
18
|
+
"relpersistence" => "table type (relpersistence)",
|
19
|
+
"reloptions" => "table options",
|
20
|
+
}
|
10
21
|
end
|
11
22
|
|
12
23
|
def columns
|
13
24
|
@columns ||= result.pg_attribute.select do |c|
|
14
25
|
c['attrelid'] == oid
|
15
|
-
end.sort_by{|c| c['
|
26
|
+
end.sort_by{|c| c['name']}.map { |row| Column.new(result, row, self) }
|
16
27
|
end
|
17
28
|
|
18
29
|
def indexes
|
@@ -1,12 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PGExaminer
|
2
4
|
class Result
|
3
5
|
class Trigger < Item
|
4
6
|
def diffable_attrs
|
5
|
-
|
7
|
+
{
|
8
|
+
"name" => "name",
|
9
|
+
"tgtype" => "trigger firing conditions (tgtype)",
|
10
|
+
}
|
6
11
|
end
|
7
12
|
|
8
13
|
def diffable_methods
|
9
|
-
|
14
|
+
{
|
15
|
+
"function" => "function"
|
16
|
+
}
|
10
17
|
end
|
11
18
|
|
12
19
|
def function
|
data/lib/pg_examiner/version.rb
CHANGED
data/pg_examiner.gemspec
CHANGED
@@ -18,7 +18,10 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
+
spec.add_dependency 'pg'
|
22
|
+
|
21
23
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
22
24
|
spec.add_development_dependency 'rake'
|
25
|
+
spec.add_development_dependency 'rspec'
|
23
26
|
spec.add_development_dependency 'pry'
|
24
27
|
end
|
data/spec/constraint_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe PGExaminer do
|
@@ -51,10 +53,10 @@ describe PGExaminer do
|
|
51
53
|
a.should_not == f
|
52
54
|
e.should_not == f
|
53
55
|
|
54
|
-
a.diff(d).should == {
|
55
|
-
a.diff(e).should == {
|
56
|
-
a.diff(f).should == {
|
57
|
-
e.diff(f).should == {
|
56
|
+
a.diff(d).should == {"schemas"=>{"public"=>{"tables"=>{"test_table"=>{"constraints"=>{"added"=>["con_two"], "removed"=>["con"]}}}}}}
|
57
|
+
a.diff(e).should == {"schemas"=>{"public"=>{"tables"=>{"test_table"=>{"constraints"=>{"added"=>["test_table_a_check"], "removed"=>["con"]}}}}}}
|
58
|
+
a.diff(f).should == {"schemas"=>{"public"=>{"tables"=>{"test_table"=>{"constraints"=>{"removed"=>["con"]}}}}}}
|
59
|
+
e.diff(f).should == {"schemas"=>{"public"=>{"tables"=>{"test_table"=>{"constraints"=>{"removed"=>["test_table_a_check"]}}}}}}
|
58
60
|
end
|
59
61
|
|
60
62
|
it "should consider foreign keys when differentiating between schemas" do
|
@@ -124,11 +126,11 @@ describe PGExaminer do
|
|
124
126
|
b.should_not == e
|
125
127
|
d.should_not == e
|
126
128
|
|
127
|
-
a.diff(c).should == {
|
128
|
-
b.diff(c).should == {
|
129
|
-
b.diff(d).should == {
|
130
|
-
b.diff(e).should == {
|
131
|
-
d.diff(e).should == {
|
129
|
+
a.diff(c).should == {"schemas"=>{"public"=>{"tables"=>{"child"=>{"constraints"=>{"child_parent_id_fkey"=>{"constraint definition"=>{"FOREIGN KEY (parent_id) REFERENCES parent(int1)"=>"FOREIGN KEY (parent_id) REFERENCES parent(int2)"}}}}}}}}
|
130
|
+
b.diff(c).should == {"schemas"=>{"public"=>{"tables"=>{"child"=>{"constraints"=>{"child_parent_id_fkey"=>{"constraint definition"=>{"FOREIGN KEY (parent_id) REFERENCES parent(int1)"=>"FOREIGN KEY (parent_id) REFERENCES parent(int2)"}}}}}}}}
|
131
|
+
b.diff(d).should == {"schemas"=>{"public"=>{"tables"=>{"child"=>{"constraints"=>{"child_parent_id_fkey"=>{"constraint definition"=>{"FOREIGN KEY (parent_id) REFERENCES parent(int1)"=>"FOREIGN KEY (parent_id) REFERENCES parent(int1) ON UPDATE CASCADE"}}}}}}}}
|
132
|
+
b.diff(e).should == {"schemas"=>{"public"=>{"tables"=>{"child"=>{"constraints"=>{"child_parent_id_fkey"=>{"constraint definition"=>{"FOREIGN KEY (parent_id) REFERENCES parent(int1)"=>"FOREIGN KEY (parent_id) REFERENCES parent(int1) ON DELETE CASCADE"}}}}}}}}
|
133
|
+
d.diff(e).should == {"schemas"=>{"public"=>{"tables"=>{"child"=>{"constraints"=>{"child_parent_id_fkey"=>{"constraint definition"=>{"FOREIGN KEY (parent_id) REFERENCES parent(int1) ON UPDATE CASCADE"=>"FOREIGN KEY (parent_id) REFERENCES parent(int1) ON DELETE CASCADE"}}}}}}}}
|
132
134
|
end
|
133
135
|
|
134
136
|
it "should consider constraints when determining table equivalency" do
|
@@ -159,8 +161,8 @@ describe PGExaminer do
|
|
159
161
|
a.should_not == c
|
160
162
|
b.should == c
|
161
163
|
|
162
|
-
a.diff(b).should == {
|
163
|
-
a.diff(c).should == {
|
164
|
+
a.diff(b).should == {"schemas"=>{"public"=>{"tables"=>{"test_table"=>{"constraints"=>{"con"=>{"constraint definition"=>{"CHECK ((a > 0)) NOT VALID"=>"CHECK ((a > 0))"}}}}}}}}
|
165
|
+
a.diff(c).should == {"schemas"=>{"public"=>{"tables"=>{"test_table"=>{"constraints"=>{"con"=>{"constraint definition"=>{"CHECK ((a > 0)) NOT VALID"=>"CHECK ((a > 0))"}}}}}}}}
|
164
166
|
end
|
165
167
|
|
166
168
|
it "should consider the tables each constraint is on" do
|
@@ -187,6 +189,6 @@ describe PGExaminer do
|
|
187
189
|
SQL
|
188
190
|
|
189
191
|
a.should_not == b
|
190
|
-
a.diff(b).should == {
|
192
|
+
a.diff(b).should == {"schemas"=>{"public"=>{"tables"=>{"test_table_1"=>{"constraints"=>{"removed"=>["con"]}}, "test_table_2"=>{"constraints"=>{"added"=>["con"]}}}}}}
|
191
193
|
end
|
192
194
|
end
|