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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 81a303d82801100e755a80fbf08ac0b6b9d0f254
4
- data.tar.gz: 0dc95a111058c548e1281a1b0694d0d97d170537
3
+ metadata.gz: 5643afd2534a1e85ecb2508240f121250c781287
4
+ data.tar.gz: cc9c1a56805634ea8d72b0ab2225b3d48fae0c21
5
5
  SHA512:
6
- metadata.gz: bcace5f639bc93c559f2ae2ab802f741a858c0a79d5071a3ba94d88b06cd26cc9f2ce3f4816d2ab6a4c3450408a706ae2b11c64821ba5fe39543be26a247c8dd
7
- data.tar.gz: 0606ea636a20e128179ec6ee679ec0b6e9445a2cf4e5ca620095f3fc52a9f3a57dd7d4c44bf69d949f87c57f67555f8e2b5a010cad532c8e83ca5cebce02a65f
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, column types, triggers, constraints, and indices. It won't care about the contents of the tables. It will care about the order the columns are in, but will ignore columns that have been dropped.
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
- 1. Aggregate functions
10
- 2. Column TOAST settings
11
- 3. Sequences
12
- 4. Views
13
- 5. User-defined types or enums
14
- 6. Object COMMENTs
15
- 7. Materialized views
16
- 8. Constraint deferral states
17
- 9. Exclusion constraints
18
- 10. Inheritance structures
19
- 11. User-defined objects in the system (pg_*) schemas or information_schema.
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
@@ -1,2 +1,7 @@
1
1
  require 'bundler/gem_tasks'
2
2
 
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new :default do |spec|
6
+ spec.pattern = './spec/**/*_spec.rb'
7
+ end
data/lib/pg_examiner.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pg_examiner/result'
2
4
  require 'pg_examiner/version'
3
5
 
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PGExaminer
2
4
  class Base
3
- def diffable_lists
4
- []
5
+ def diffable_attrs
6
+ {}
5
7
  end
6
8
 
7
- def diffable_attrs
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.to_s)
22
- that = other.row.fetch(attr.to_s)
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[attr] = {this => that}
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[attr] = {this => that}
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[attr] = result
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[:added] = added if added.any?
60
- h[:removed] = removed if removed.any?
61
- r[attr] = h
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
 
@@ -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
- [:schemas, :extensions, :languages]
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 tgconstrrelid = '0' -- Ignore foreign key triggers, which have unpredictable names.
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
- [:type, :default]
7
+ {
8
+ "type" => "type",
9
+ "default" => "default"
10
+ }
6
11
  end
7
12
 
8
13
  def diffable_attrs
9
- [:name, :attndims, :attnotnull, :atttypmod]
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,8 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PGExaminer
2
4
  class Result
3
5
  class Constraint < Item
4
6
  def diffable_attrs
5
- [:name, :definition]
7
+ {
8
+ "name" => "name",
9
+ "definition" => "constraint definition",
10
+ }
6
11
  end
7
12
  end
8
13
  end
@@ -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
- [:name, :extversion]
7
+ {
8
+ "name" => "name",
9
+ "extversion" => "extension version",
10
+ }
6
11
  end
7
12
 
8
13
  def diffable_methods
9
- [:schema_name]
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
- [:name, :proargmodes, :definition]
7
+ {
8
+ "name" => "name",
9
+ "proargmodes" => "argument modes",
10
+ "definition" => "function definition",
11
+ }
6
12
  end
7
13
 
8
14
  def diffable_methods
9
- [:argument_types, :return_type, :language]
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
- [:name, :filter, :indisunique, :indisprimary]
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
- [:expression]
16
+ {
17
+ "expression" => "expression"
18
+ }
10
19
  end
11
20
 
12
21
  def expression
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PGExaminer
2
4
  class Result
3
5
  class Item < Base
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PGExaminer
2
4
  class Result
3
5
  class Language < Item
4
6
  def diffable_attrs
5
- [:name]
7
+ {
8
+ "name" => "name"
9
+ }
6
10
  end
7
11
  end
8
12
  end
@@ -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
- [:tables, :functions]
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
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PGExaminer
4
+ class Result
5
+ class Sequence < Item
6
+ def diffable_lists
7
+ {}
8
+ end
9
+
10
+ def diffable_attrs
11
+ {}
12
+ end
13
+ end
14
+ end
15
+ end
@@ -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
- [:columns, :indexes, :constraints, :triggers]
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
- [:name, :relpersistence, :reloptions]
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['attnum'].to_i}.map { |row| Column.new(result, row, self) }
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
- [:name, :tgtype]
7
+ {
8
+ "name" => "name",
9
+ "tgtype" => "trigger firing conditions (tgtype)",
10
+ }
6
11
  end
7
12
 
8
13
  def diffable_methods
9
- [:function]
14
+ {
15
+ "function" => "function"
16
+ }
10
17
  end
11
18
 
12
19
  def function
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PGExaminer
2
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
3
5
  end
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
@@ -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 == {:schemas=>{"public"=>{:tables=>{"test_table"=>{:constraints=>{:added=>["con_two"], :removed=>["con"]}}}}}}
55
- a.diff(e).should == {:schemas=>{"public"=>{:tables=>{"test_table"=>{:constraints=>{:added=>["test_table_a_check"], :removed=>["con"]}}}}}}
56
- a.diff(f).should == {:schemas=>{"public"=>{:tables=>{"test_table"=>{:constraints=>{:removed=>["con"]}}}}}}
57
- e.diff(f).should == {:schemas=>{"public"=>{:tables=>{"test_table"=>{:constraints=>{:removed=>["test_table_a_check"]}}}}}}
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 == {:schemas=>{"public"=>{:tables=>{"child"=>{:constraints=>{"child_parent_id_fkey"=>{:definition=>{"FOREIGN KEY (parent_id) REFERENCES parent(int1)"=>"FOREIGN KEY (parent_id) REFERENCES parent(int2)"}}}}}}}}
128
- b.diff(c).should == {:schemas=>{"public"=>{:tables=>{"child"=>{:constraints=>{"child_parent_id_fkey"=>{:definition=>{"FOREIGN KEY (parent_id) REFERENCES parent(int1)"=>"FOREIGN KEY (parent_id) REFERENCES parent(int2)"}}}}}}}}
129
- b.diff(d).should == {:schemas=>{"public"=>{:tables=>{"child"=>{:constraints=>{"child_parent_id_fkey"=>{:definition=>{"FOREIGN KEY (parent_id) REFERENCES parent(int1)"=>"FOREIGN KEY (parent_id) REFERENCES parent(int1) ON UPDATE CASCADE"}}}}}}}}
130
- b.diff(e).should == {:schemas=>{"public"=>{:tables=>{"child"=>{:constraints=>{"child_parent_id_fkey"=>{:definition=>{"FOREIGN KEY (parent_id) REFERENCES parent(int1)"=>"FOREIGN KEY (parent_id) REFERENCES parent(int1) ON DELETE CASCADE"}}}}}}}}
131
- d.diff(e).should == {:schemas=>{"public"=>{:tables=>{"child"=>{:constraints=>{"child_parent_id_fkey"=>{:definition=>{"FOREIGN KEY (parent_id) REFERENCES parent(int1) ON UPDATE CASCADE"=>"FOREIGN KEY (parent_id) REFERENCES parent(int1) ON DELETE CASCADE"}}}}}}}}
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 == {:schemas=>{"public"=>{:tables=>{"test_table"=>{:constraints=>{"con"=>{:definition=>{"CHECK ((a > 0)) NOT VALID"=>"CHECK ((a > 0))"}}}}}}}}
163
- a.diff(c).should == {:schemas=>{"public"=>{:tables=>{"test_table"=>{:constraints=>{"con"=>{:definition=>{"CHECK ((a > 0)) NOT VALID"=>"CHECK ((a > 0))"}}}}}}}}
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 == {:schemas=>{"public"=>{:tables=>{"test_table_1"=>{:constraints=>{:removed=>["con"]}}, "test_table_2"=>{:constraints=>{:added=>["con"]}}}}}}
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