pgdiff 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0ed8304d8136891ddc01a5de766d293ef7b95534
4
+ data.tar.gz: 955748783f3ee852a7fe09cff074c4f2e42808c6
5
+ SHA512:
6
+ metadata.gz: 7907cd30ec595f41d7a8ab10faa38cf0b54cde413d9ed7e34ef6a3ec1d73037334cd82acf160c25387e0f3e117feb98e68590f293261a06c3d84dcc00a5ad8e7
7
+ data.tar.gz: aae251d848b7d31af3082850ca4e9e482577cb081dcf246bfa458aa5bae0cbb10f37cd273207ed2034066b095994bd69f20b983dd610f3fdcf868cc4bb60c6a2
@@ -0,0 +1,25 @@
1
+ = Changelog
2
+
3
+ === 1.0.0 (January 31, 2015)
4
+ * Improve constraint checking
5
+ * Change output ordering
6
+ * Wrap up as Ruby Gem
7
+
8
+ === 0.6.0 (July 20, 2011)
9
+ * Fix constraint checking for postgresql 9.0
10
+
11
+ === 0.5.0 (August 12, 2010)
12
+ * Fix script due to changes in pg gem where returned rows are hash tables and not arrays.
13
+ * Fix looking up index names
14
+
15
+ === 0.4.0 (November 21, 2009)
16
+ * Fix to work with new pg gem
17
+
18
+ === 0.3.0 (March 30, 2009)
19
+ * Added indexes were not reported correctly.
20
+
21
+ === 0.2.0 (April 12, 2007)
22
+ * Added ability to check differences in table field orders
23
+
24
+ === 0.1.0 (December 7, 2005)
25
+ * Original version posted to http://www.dzone.com/snippets/pgdiff-compare-two-postgresql
@@ -0,0 +1,54 @@
1
+ = pgdiff
2
+
3
+ == Overview
4
+
5
+ This ruby gem provides a pgdiff script that compares two PostgreSQL databases and generates SQL statements to make
6
+ their structures the same. The original version was posted at http://www.dzone.com/snippets/pgdiff-compare-two-postgresql.
7
+
8
+ The script detects differences in:
9
+
10
+ * Domains
11
+ * Schemas
12
+ * Tables
13
+ * Table field order
14
+ * Sequences
15
+ * Views
16
+ * Constraints
17
+ * Indices
18
+ * Functions
19
+ * Triggers
20
+ * Rules
21
+
22
+ Two objects with the same name are considered equal if they have the same definitions.
23
+
24
+ pgdiff does not currently compare ownership, user rights, object dependencies, table inheritance, type casts, aggregates
25
+ or operators. Patches are welcome to add this functionality.
26
+
27
+ == Installation
28
+
29
+ Install pgdiff using Ruby Gems:
30
+
31
+ gem install "pgdiff"
32
+
33
+ == Usage
34
+
35
+ To use pgdiff open a command prompt and runn the following command:
36
+
37
+ pg_diff "source_connection_string" "destination_connection_string"
38
+
39
+ The format of the two connection strings is documented in the Ruby pg gem. For more information
40
+ see http://www.rubydoc.info/gems/pg/PG/Connection:initialize
41
+
42
+ == Output
43
+
44
+ pgdiff will output the necesssary sql statements to update the source database to have the same structure
45
+ as the destination database. PLEASE VERIFY the accuracy of the generated sql statements before running them,
46
+ it is always possible the script has bugs.
47
+
48
+ == Support
49
+
50
+ If you have any questions or want to contribute to pgdiff please visit https://github.com/cfis/pgdiff.git
51
+
52
+ == License
53
+
54
+ pgdiff is provided under the MIT license.
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'rubygems/package_task'
5
+
6
+ GEM_NAME = 'pgdiff'
7
+
8
+ # Read the spec file
9
+ spec = Gem::Specification.load("#{GEM_NAME}.gemspec")
10
+
11
+ # Setup generic gem
12
+ Gem::PackageTask.new(spec) do |pkg|
13
+ pkg.package_dir = 'pkg'
14
+ pkg.need_tar = false
15
+ end
@@ -0,0 +1,31 @@
1
+ #!/bin/env ruby
2
+
3
+ # This is a simple approach to track database schema changes in PostgreSQL.
4
+ # In some way it is similar to diff program, finding out structure changes
5
+ # and results in SQL script to upgrade to new schema.
6
+ #
7
+ # Differences are tracked on schemas, domains, sequences, views, tables, indices, constraints, rules, functions, triggers.
8
+ # Two objects with the same name are considered equal if they have the same definitions.
9
+ #
10
+ # Missing features: tracking of ownership, user rights, object dependencies, table inheritance, type casts, aggregates, operators.
11
+ #
12
+ # Usage:
13
+ # ./pg_diff source_connection_string destination_connection_string
14
+ #
15
+ # The format of the connection strings is documented in the Ruby pg gem
16
+ # at http://www.rubydoc.info/gems/pg/PG/Connection:initialize.
17
+
18
+ require 'pg'
19
+ require 'pgdiff'
20
+
21
+ if ARGV.length != 2
22
+ raise(ArgumentError, "You must specify two arguments - a source and target db connection string. For " +
23
+ "more information about how to format connection strings see http://www.rubydoc.info/gems/pg/PG/Connection:initialize")
24
+ end
25
+
26
+ source_db = ARGV[0]
27
+ target_db = ARGV[1]
28
+
29
+ diff = PgDiff::Diff.new(ARGV[0], ARGV[1])
30
+ diff.run_compare
31
+ puts diff.output
@@ -0,0 +1,23 @@
1
+ module PgDiff
2
+ class Attribute
3
+ attr_accessor :name, :type_def, :notnull, :default
4
+
5
+ def initialize(name, typedef, notnull, default)
6
+ @name = name
7
+ @type_def = typedef
8
+ @notnull = notnull
9
+ @default = default
10
+ end
11
+
12
+ def definition
13
+ out = [' ', @name, @type_def]
14
+ out << 'NOT NULL' if @notnull
15
+ out << 'DEFAULT ' + @default if @default
16
+ out.join(" ")
17
+ end
18
+
19
+ def == (other)
20
+ definition == other.definition
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,112 @@
1
+ module PgDiff
2
+ class Database
3
+ attr_accessor :tables, :views, :sequences, :schemas, :domains, :rules, :functions, :triggers
4
+
5
+ def initialize(conn)
6
+ cls_query = <<-EOT
7
+ SELECT n.nspname, c.relname, c.relkind
8
+ FROM pg_catalog.pg_class c
9
+ LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
10
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
11
+ WHERE c.relkind IN ('r','S','v')
12
+ AND n.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema')
13
+ ORDER BY 1,2;
14
+ EOT
15
+ @views = {}
16
+ @tables = {}
17
+ @sequences = {}
18
+ @schemas = {}
19
+ @domains = {}
20
+ @functions = {}
21
+ @rules = {}
22
+ @triggers = {}
23
+
24
+ conn.query(cls_query).each do |tuple|
25
+ schema = tuple['nspname']
26
+ relname = tuple['relname']
27
+ relkind = tuple['relkind']
28
+ case relkind
29
+ when 'r'
30
+ @tables["#{schema}.#{relname}"] = Table.new(conn, schema, relname)
31
+ when 'v'
32
+ @views["#{schema}.#{relname}"] = View.new(conn, schema, relname)
33
+ when 'S'
34
+ @sequences["#{schema}.#{relname}"] = Sequence.new(conn, schema, relname)
35
+ end
36
+ end
37
+
38
+ domain_qry = <<-EOT
39
+ SELECT n.nspname, t.typname, pg_catalog.format_type(t.typbasetype, t.typtypmod) || ' ' ||
40
+ CASE WHEN t.typnotnull AND t.typdefault IS NOT NULL THEN 'not null default '||t.typdefault
41
+ WHEN t.typnotnull AND t.typdefault IS NULL THEN 'not null'
42
+ WHEN NOT t.typnotnull AND t.typdefault IS NOT NULL THEN 'default '|| t.typdefault
43
+ ELSE ''
44
+ END AS def
45
+ FROM pg_catalog.pg_type t
46
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
47
+ WHERE t.typtype = 'd'
48
+ ORDER BY 1, 2
49
+ EOT
50
+ conn.query(domain_qry).each do |tuple|
51
+ schema = tuple['nspname']
52
+ typename = tuple['typname']
53
+ value = tuple['def']
54
+ @domains["#{schema}.#{typename}"] = value
55
+ end
56
+
57
+ schema_qry = <<-EOT
58
+ select nspname from pg_namespace
59
+ EOT
60
+ conn.query(schema_qry).each do |tuple|
61
+ schema = tuple['nspname']
62
+ @schemas[schema] = schema
63
+ end
64
+
65
+ func_query = <<-EOT
66
+ SELECT proname AS function_name
67
+ , nspname AS namespace
68
+ , lanname AS language_name
69
+ , pg_catalog.obj_description(pg_proc.oid, 'pg_proc') AS comment
70
+ , proargtypes AS function_args
71
+ , proargnames AS function_arg_names
72
+ , prosrc AS source_code
73
+ , proretset AS returns_set
74
+ , prorettype AS return_type,
75
+ provolatile, proisstrict, prosecdef
76
+ FROM pg_catalog.pg_proc
77
+ JOIN pg_catalog.pg_language ON (pg_language.oid = prolang)
78
+ JOIN pg_catalog.pg_namespace ON (pronamespace = pg_namespace.oid)
79
+ JOIN pg_catalog.pg_type ON (prorettype = pg_type.oid)
80
+ WHERE pg_namespace.nspname !~ 'pg_catalog|information_schema'
81
+ AND proname != 'plpgsql_call_handler'
82
+ AND proname != 'plpgsql_validator'
83
+ EOT
84
+
85
+ conn.exec(func_query).each_with_index do |tuple, i|
86
+ func = Function.new(conn, tuple)
87
+ @functions[func.signature] = func
88
+ end
89
+
90
+ rule_query = <<-EOT
91
+ select schemaname || '.' || tablename || '.' || rulename as rule_name,
92
+ schemaname || '.' || tablename as tab_name,
93
+ rulename, definition
94
+ from pg_rules
95
+ where schemaname !~ 'pg_catalog|information_schema'
96
+ EOT
97
+ conn.exec(rule_query).each do |tuple|
98
+ @rules[tuple['rule_name']] = Rule.new(tuple['tab_name'], tuple['rulename'], tuple['definition'])
99
+ end
100
+
101
+ trigger_query = <<-EOT
102
+ select nspname || '.' || relname as tgtable, tgname, pg_get_triggerdef(t.oid) as tg_def
103
+ from pg_trigger t join pg_class c ON (tgrelid = c.oid ) JOIN pg_namespace n ON (c.relnamespace = n.oid)
104
+ where not tgisinternal
105
+ and nspname !~ 'pg_catalog|information_schema'
106
+ EOT
107
+ conn.exec(trigger_query).each do |tuple|
108
+ @triggers[tuple['tgtable'] + "." + tuple['tgname']] = Trigger.new(tuple['tgtable'], tuple['tgname'], tuple['tg_def'])
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,345 @@
1
+ module PgDiff
2
+ class Diff
3
+ def initialize(old_db_spec, new_db_spec)
4
+ @old_conn = PGconn.new(old_db_spec)
5
+ @new_conn = PGconn.new(new_db_spec)
6
+ @sections = [
7
+ :domains_drop,
8
+ :domains_create,
9
+ :schemas_drop,
10
+ :schemas_create,
11
+ :tables_drop,
12
+ :tables_change,
13
+ :tables_create,
14
+ :sequences_drop,
15
+ :sequences_create,
16
+ :views_drop,
17
+ :views_create,
18
+ :constraints_drop,
19
+ :constraints_change,
20
+ :constraints_create,
21
+ :indices_drop,
22
+ :indices_create,
23
+ :functions_drop,
24
+ :functions_create ,
25
+ :triggers_drop,
26
+ :triggers_create ,
27
+ :rules_drop,
28
+ :rules_create
29
+ ]
30
+ @script = {}
31
+ @sections.each {|s| @script[s] = []}
32
+ end
33
+
34
+ def run_compare
35
+ @old_database = Database.new(@old_conn)
36
+ @new_database = Database.new(@new_conn)
37
+ compare_schemas
38
+ compare_domains
39
+ compare_sequences
40
+ compare_triggers_drop
41
+ compare_rules_drop
42
+ compare_views_drop
43
+ compare_table_attrs
44
+ compare_views_create
45
+ compare_functions
46
+ compare_rules_create
47
+ compare_triggers_create
48
+ compare_table_constraints
49
+ end
50
+
51
+ def add_script(section, statement)
52
+ @script[section] << statement
53
+ end
54
+
55
+ def compare_schemas
56
+ @old_database.schemas.keys.each do |name|
57
+ add_script(:schemas_drop , "DROP SCHEMA #{name};") unless @new_database.schemas.has_key?(name)
58
+ end
59
+ @new_database.schemas.keys.each do |name|
60
+ add_script(:schemas_create , "CREATE SCHEMA #{name};") unless @old_database.schemas.has_key?(name)
61
+ end
62
+ end
63
+
64
+ def compare_domains
65
+ @old_database.domains.keys.each do |name|
66
+ add_script(:domains_drop , "DROP DOMAIN #{name} CASCADE;") unless @new_database.domains.has_key?(name)
67
+ end
68
+ @new_database.domains.each do |name, df|
69
+ add_script(:domains_create , "CREATE DOMAIN #{name} AS #{df};") unless @old_database.domains.has_key?(name)
70
+ old_domain = @old_database.domains[name]
71
+ if old_domain && old_domain != df
72
+ add_script(:domains_drop, "DROP DOMAIN #{name} CASCADE;")
73
+ add_script(:domains_create, "-- [changed domain] :")
74
+ add_script(:domains_create, "-- OLD: #{old_domain}")
75
+ add_script(:domains_create, "CREATE DOMAIN #{name} AS #{df};")
76
+ end
77
+ end
78
+ end
79
+
80
+ def compare_sequences
81
+ @old_database.sequences.keys.each do |name|
82
+ add_script(:sequences_drop , "DROP SEQUENCE #{name} CASCADE;") unless @new_database.sequences.has_key?(name)
83
+ end
84
+ @new_database.sequences.keys.each do |name|
85
+ add_script(:sequences_create , "CREATE SEQUENCE #{name};") unless @old_database.sequences.has_key?(name)
86
+ end
87
+ end
88
+
89
+ def compare_functions
90
+ @old_database.functions.keys.each do |name|
91
+ add_script(:functions_drop , "DROP FUNCTION #{name} CASCADE;") unless @new_database.functions.has_key?(name)
92
+ end
93
+ @new_database.functions.each do |name, func|
94
+ add_script(:functions_create , func.definition) unless @old_database.functions.has_key?(name)
95
+ old_function = @old_database.functions[name]
96
+ if old_function && old_function.definition != func.definition
97
+ add_script(:functions_create , '-- [changed function] :')
98
+ add_script(:functions_create , '-- OLD :')
99
+ add_script(:functions_create , old_function.definition.gsub(/^/, "--> ") )
100
+ add_script(:functions_create , func.definition)
101
+ end
102
+ end
103
+ end
104
+
105
+ def compare_rules_drop
106
+ @old_database.rules.each do |name, rule|
107
+ add_script(:rules_drop , "DROP RULE #{rule.name} ON #{rule.table_name} CASCADE;") unless @new_database.rules.has_key?(name)
108
+ end
109
+ end
110
+
111
+ def compare_rules_create
112
+ @new_database.rules.each do |name, rule|
113
+ add_script(:rules_create , rule.definition) unless @old_database.rules.has_key?(name)
114
+ old_rule = @old_database.rules[name]
115
+ if old_rule && old_rule != rule
116
+ add_script(:rules_drop , "DROP RULE #{rule.name} ON #{rule.table_name} CASCADE;")
117
+ add_script(:rules_create , "-- [changed rule] :")
118
+ add_script(:rules_create , "-- OLD: #{old_rule.definition}")
119
+ add_script(:rules_create , rule.definition )
120
+ end
121
+ end
122
+ end
123
+
124
+ def compare_triggers_drop
125
+ @old_database.triggers.each do |name, trigger|
126
+ add_script(:triggers_drop , "DROP trigger #{trigger.name} ON #{trigger.table_name} CASCADE;") unless @new_database.triggers.has_key?(name)
127
+ end
128
+ end
129
+
130
+ def compare_triggers_create
131
+ @new_database.triggers.each do |name, trigger|
132
+ add_script(:triggers_create , trigger.definition) unless @old_database.triggers.has_key?(name)
133
+ old_trigger = @old_database.triggers[name]
134
+ if old_trigger && old_trigger != trigger
135
+ add_script(:triggers_drop , "DROP trigger #{trigger.name} ON #{trigger.table_name} CASCADE;")
136
+ add_script(:triggers_create , "-- [changed trigger] :")
137
+ add_script(:triggers_create , "-- OLD #{old_trigger.definition}")
138
+ add_script(:triggers_create , trigger.definition)
139
+ end
140
+ end
141
+ end
142
+
143
+ def compare_views_drop
144
+ @old_database.views.keys.each do |name|
145
+ add_script(:views_drop , "DROP VIEW #{name};") unless @new_database.views.has_key?(name)
146
+ end
147
+ end
148
+
149
+ def compare_views_create
150
+ @new_database.views.each do |name, df|
151
+ add_script(:views_create , df.definition) unless @old_database.views.has_key?(name)
152
+ old_view = @old_database.views[name]
153
+ if old_view && df.definition != old_view.definition
154
+ add_script(:views_drop , "DROP VIEW #{name};")
155
+ add_script(:views_create , "-- [changed view] :")
156
+ add_script(:views_create , "-- #{old_view.definition.gsub(/\n/, ' ')}")
157
+ add_script(:views_create , df.definition)
158
+ end
159
+ end
160
+ end
161
+
162
+ def compare_table_attrs
163
+ @old_database.tables.each do |name, table|
164
+ add_script(:tables_drop, "DROP TABLE #{name} CASCADE;") unless @new_database.tables.has_key?(name)
165
+ end
166
+ @to_compare = []
167
+ @new_database.tables.each do |name, table|
168
+ unless @old_database.tables.has_key?(name)
169
+ add_script(:tables_create , table.table_creation)
170
+ add_script(:indices_create , table.index_creation) unless table.indexes.empty?
171
+ @to_compare << name
172
+ else
173
+ diff_attributes(@old_database.tables[name], table)
174
+ diff_indexes(@old_database.tables[name], table)
175
+ @to_compare << name
176
+ end
177
+ end
178
+ end
179
+
180
+ def compare_table_constraints
181
+ @c_check = []
182
+ @c_primary = []
183
+ @c_unique = []
184
+ @c_foreign = []
185
+ @to_compare.each do |name|
186
+ if @old_database.tables[name]
187
+ diff_constraints(@old_database.tables[name], @new_database.tables[name])
188
+ else
189
+ @new_database.tables[name].constraints.each do |cname, cdef|
190
+ add_cnstr(name, cname, cdef)
191
+ end
192
+ end
193
+ end
194
+ @script[:constraints_create] += @c_check
195
+ @script[:constraints_create] += @c_primary
196
+ @script[:constraints_create] += @c_unique
197
+ @script[:constraints_create] += @c_foreign
198
+ end
199
+
200
+ def output
201
+ out = []
202
+ @sections.each do |sect|
203
+ if @script[sect].empty?
204
+ out << "-- [SKIP SECTION : #{sect.to_s.upcase}] : no changes\n"
205
+ else
206
+ out << "-- [START SECTION : #{sect.to_s.upcase}]"
207
+ out += @script[sect]
208
+ out << "-- [END SECTION : #{sect.to_s.upcase}]\n"
209
+ end
210
+ end
211
+ out.join("\n")
212
+ end
213
+
214
+ def diff_attributes(old_table, new_table)
215
+ dropped = []
216
+ added = []
217
+ changed = []
218
+
219
+ order = []
220
+ old_table.attributes.keys.each do |attname|
221
+ if new_table.has_attribute?(attname)
222
+ changed << attname if old_table.attributes[attname] != new_table.attributes[attname]
223
+ else
224
+ dropped << attname
225
+ end
226
+ end
227
+
228
+ old_table.attributes.keys.each do |attname|
229
+ if new_table.has_attribute?(attname)
230
+ old_index = old_table.attribute_index(attname)
231
+ new_index = new_table.attribute_index(attname)
232
+ order << attname if old_index != new_index
233
+ end
234
+ end
235
+ new_table.attributes.keys.each do |attname|
236
+ added << attname unless old_table.has_attribute?(attname)
237
+ end
238
+ add_script(:tables_change , "-- [#{old_table.name}] dropped attributes") unless dropped.empty?
239
+ dropped.each do |attname|
240
+ add_script(:tables_change , "ALTER TABLE #{old_table.name} DROP COLUMN #{attname} CASCADE;")
241
+ end
242
+ add_script(:tables_change , "-- [#{old_table.name}] added attributes") unless added.empty?
243
+ added.each do |attname|
244
+ add_script(:tables_change , "ALTER TABLE #{old_table.name} ADD COLUMN #{new_table.attributes[attname].definition};")
245
+ end
246
+ add_script(:tables_change , "-- [#{old_table.name}] changed attributes") unless changed.empty?
247
+ changed.each do |attname|
248
+ old_att = old_table.attributes[attname]
249
+ new_att = new_table.attributes[attname]
250
+ add_script(:tables_change , "-- attribute: #{attname}")
251
+ add_script(:tables_change , "-- OLD : #{old_att.definition}")
252
+ add_script(:tables_change , "-- NEW : #{new_att.definition}")
253
+ if old_att.type_def != new_att.type_def
254
+ add_script(:tables_change , "ALTER TABLE #{old_table.name} ALTER COLUMN #{attname} TYPE #{new_att.type_def};")
255
+ end
256
+ if old_att.default != new_att.default
257
+ if new_att.default.nil?
258
+ add_script(:tables_change , "ALTER TABLE #{old_table.name} ALTER COLUMN #{attname} DROP DEFAULT;")
259
+ else
260
+ add_script(:tables_change , "ALTER TABLE #{old_table.name} ALTER COLUMN #{attname} SET DEFAULT #{new_att.default};")
261
+ end
262
+ end
263
+ if old_att.notnull != new_att.notnull
264
+ add_script(:tables_change , "ALTER TABLE #{old_table.name} ALTER COLUMN #{attname} #{new_att.notnull ? 'SET' : 'DROP'} NOT NULL;")
265
+ end
266
+ end
267
+
268
+ add_script(:tables_change , "-- [#{old_table.name}] attribute order changed") unless order.empty?
269
+ order.each do |attname|
270
+ add_script(:tables_change , " #{attname}. Old index: #{old_table.attribute_index(attname)}, New index: #{new_table.attribute_index(attname)}")
271
+ end
272
+ end
273
+
274
+ def diff_constraints(old_table, new_table)
275
+ dropped = []
276
+ added = []
277
+ changed = []
278
+
279
+ old_table.constraints.keys.each do |conname|
280
+ if new_table.has_constraint?(conname)
281
+ if old_table.constraints[conname] != new_table.constraints[conname]
282
+ changed << conname
283
+ end
284
+ else
285
+ dropped << conname
286
+ end
287
+ end
288
+
289
+ new_table.constraints.keys.each do |conname|
290
+ added << conname unless old_table.has_constraint?(conname)
291
+ end
292
+
293
+ dropped.each do |name|
294
+ add_script(:constraints_drop , "ALTER TABLE #{old_table.name} DROP CONSTRAINT #{name};")
295
+ end
296
+
297
+ added.each do |name|
298
+ add_cnstr(old_table.name, name, new_table.constraints[name])
299
+ end
300
+
301
+ changed.each do |name|
302
+ add_script(:constraints_change,
303
+ "-- Previous: #{old_table.constraints[name]}\n" +
304
+ "ALTER TABLE #{old_table.name} DROP CONSTRAINT #{name};\n" +
305
+ "ALTER TABLE #{new_table.name} ADD CONSTRAINT #{name} #{new_table.constraints[name]};\n")
306
+ end
307
+ end
308
+
309
+ def add_cnstr(tablename, cnstrname, cnstrdef)
310
+ c_string = "ALTER TABLE #{tablename} ADD CONSTRAINT #{cnstrname} #{cnstrdef} ;"
311
+ case cnstrdef
312
+ when /^CHECK / then @c_check << c_string
313
+ when /^PRIMARY / then @c_primary << c_string
314
+ when /^FOREIGN / then @c_foreign << c_string
315
+ when /^UNIQUE / then @c_unique << c_string
316
+ end
317
+ end
318
+
319
+ def diff_indexes(old_table, new_table)
320
+ dropped = []
321
+ added = []
322
+
323
+ old_table.indexes.keys.each do |name|
324
+ if new_table.has_index?(name)
325
+ if old_table.indexes[name] != new_table.indexes[name]
326
+ dropped << name
327
+ added << name
328
+ end
329
+ else
330
+ dropped << name
331
+ end
332
+ end
333
+ new_table.indexes.keys.each do |name|
334
+ added << name unless old_table.has_index?(name)
335
+ end
336
+
337
+ dropped.each do |name|
338
+ add_script(:indices_drop , "DROP INDEX #{name};")
339
+ end
340
+ added.each do |name|
341
+ add_script(:indices_create , (new_table.indexes[name] + ";")) if new_table.indexes[name]
342
+ end
343
+ end
344
+ end
345
+ end
@@ -0,0 +1,56 @@
1
+ module PgDiff
2
+ class Function
3
+ def initialize(conn, tuple)
4
+ @name = tuple['namespace'] + "." + tuple['function_name']
5
+ @language = tuple['language_name']
6
+ @src = tuple['source_code']
7
+ @returns_set = tuple['returns_set']
8
+ @return_type = format_type(conn, tuple['return_type'])
9
+ @tipes = tuple['function_args'].split(" ")
10
+ if tuple['function_arg_names'] && tuple['function_arg_names'] =~ /^\{(.*)\}$/
11
+ @arnames = $1.split(',')
12
+ elsif tuple['function_arg_names'].is_a? Array # my version of ruby-postgres
13
+ @arnames = tuple['function_arg_names']
14
+ else
15
+ @arnames = [""] * @tipes.length
16
+ end
17
+ alist = []
18
+ @tipes.each_with_index do |typ,idx|
19
+ alist << (@arnames[idx] + " " + format_type(conn, typ))
20
+ end
21
+ @arglist = alist.join(" , ")
22
+ @strict = tuple['proisstrict'] ? ' STRICT' : ''
23
+ @secdef = tuple['prosecdef'] ? ' SECURITY DEFINER' : ''
24
+ @volatile = case tuple['provolatile']
25
+ when 'i' then ' IMMUTABLE'
26
+ when 's' then ' STABLE'
27
+ else ''
28
+ end
29
+ end
30
+
31
+ def signature
32
+ "#{@name}(#{@arglist})"
33
+ end
34
+
35
+ def definition
36
+ <<-EOT
37
+ CREATE OR REPLACE FUNCTION #{@name} (#{@arglist}) RETURNS #{@returns_set ? 'SETOF' : ''} #{@return_type} AS $_$#{@src}$_$ LANGUAGE '#{@language}' #{@volatile}#{@strict}#{@secdef};
38
+ EOT
39
+ end
40
+
41
+ def == (other)
42
+ definition == other.definition
43
+ end
44
+
45
+ def format_type(conn, oid)
46
+ t_query = <<-EOT
47
+ SELECT pg_catalog.format_type(pg_type.oid, typtypmod) AS type_name
48
+ FROM pg_catalog.pg_type
49
+ JOIN pg_catalog.pg_namespace ON (pg_namespace.oid = typnamespace)
50
+ WHERE pg_type.oid =
51
+ EOT
52
+ tuple = conn.query(t_query + oid.to_s).first
53
+ tuple['type_name']
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,9 @@
1
+ require 'database'
2
+ require 'table'
3
+ require 'attribute'
4
+ require 'view'
5
+ require 'sequence'
6
+ require 'rule'
7
+ require 'function'
8
+ require 'trigger'
9
+ require 'diff'
@@ -0,0 +1,15 @@
1
+ module PgDiff
2
+ class Rule
3
+ attr_reader :table_name, :name, :definition
4
+
5
+ def initialize(table_name, name, df)
6
+ @table_name = table_name
7
+ @name = name
8
+ @definition = df
9
+ end
10
+
11
+ def == (other)
12
+ other.definition == definition
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module PgDiff
2
+ class Sequence
3
+ def initialize(conn, sch, relname)
4
+ @name = "#{sch}.#{relname}"
5
+ end
6
+
7
+ def definition
8
+ "CREATE SEQUENCE #{@name} ;"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,90 @@
1
+ module PgDiff
2
+ class Table
3
+ attr_accessor :table_name, :schema, :attributes, :constraints, :indexes
4
+
5
+ def initialize(conn, schema, table_name)
6
+ @schema = schema
7
+ @table_name = table_name
8
+ @attributes = {}
9
+ @constraints = {}
10
+ @indexes = {}
11
+ @atlist = []
12
+
13
+ att_query = <<-EOT
14
+ select attname, format_type(atttypid, atttypmod) as a_type, attnotnull, pg_get_expr(adbin, attrelid) as a_default
15
+ from pg_attribute left join pg_attrdef on (adrelid = attrelid and adnum = attnum)
16
+ where attrelid = '#{schema}.#{table_name}'::regclass and not attisdropped and attnum > 0
17
+ order by attnum
18
+ EOT
19
+ conn.query(att_query).each do |tuple|
20
+ attname = tuple['attname']
21
+ typedef = tuple['a_type']
22
+ notnull = tuple['attnotnull']
23
+ default = tuple['a_default']
24
+ @attributes[attname] = Attribute.new(attname, typedef, notnull, default)
25
+ @atlist << attname
26
+ end
27
+
28
+ ind_query = <<-EOT
29
+ select indexrelid::regclass as indname, pg_get_indexdef(indexrelid) as def
30
+ from pg_index where indrelid = '#{schema}.#{table_name}'::regclass and not indisprimary
31
+ EOT
32
+ conn.query(ind_query).each do |tuple|
33
+ name = tuple['indname']
34
+ value = tuple['def']
35
+ @indexes[name] = value
36
+ end
37
+
38
+ cons_query = <<-EOT
39
+ select conname, pg_get_constraintdef(oid) from pg_constraint where conrelid = '#{schema}.#{table_name}'::regclass
40
+ EOT
41
+ conn.query(cons_query).each do |tuple|
42
+ name = tuple['conname']
43
+ value = tuple['pg_get_constraintdef']
44
+ @constraints[name] = value
45
+ end
46
+ @constraints.keys.each do |cname|
47
+ @indexes.delete("#{schema}.#{cname}") if has_index?(cname)
48
+ end
49
+ end
50
+
51
+ def has_attribute?(name)
52
+ @attributes.has_key?(name)
53
+ end
54
+
55
+ def attribute_index(name)
56
+ @atlist.index(name)
57
+ end
58
+
59
+ def has_index?(name)
60
+ @indexes.has_key?(name) || @indexes.has_key?("#{schema}.#{name}")
61
+ end
62
+
63
+ def has_constraint?(name)
64
+ @constraints.has_key?(name)
65
+ end
66
+
67
+ def table_creation
68
+ out = ["CREATE TABLE #{name} ("]
69
+ stmt = []
70
+ @atlist.each do |attname|
71
+ stmt << @attributes[attname].definition
72
+ end
73
+ out << stmt.join(",\n")
74
+ out << ");"
75
+ out.join("\n")
76
+ end
77
+
78
+ def name
79
+ "#{schema}.#{table_name}"
80
+ end
81
+
82
+ def index_creation
83
+ out = []
84
+ @indexes.values.each do |c|
85
+ out << (c+";")
86
+ end
87
+ out.join("\n")
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,15 @@
1
+ module PgDiff
2
+ class Trigger
3
+ attr_reader :table_name, :name, :definition
4
+
5
+ def initialize(table_name, name, df)
6
+ @table_name = table_name
7
+ @name = name
8
+ @definition = df + ";"
9
+ end
10
+
11
+ def == (other)
12
+ other.definition == definition
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ module PgDiff
2
+ class View
3
+ attr_reader :def, :name
4
+
5
+ def initialize(conn, sch, relname)
6
+ @name = "#{sch}.#{relname}"
7
+ view_qery = <<-EOT
8
+ SELECT pg_catalog.pg_get_viewdef('#{@name}'::regclass, true)
9
+ EOT
10
+ tuple = conn.query(view_qery).first
11
+ @def = tuple['pg_get_viewdef']
12
+ end
13
+
14
+ def definition
15
+ "CREATE VIEW #{@name} AS #{@def}"
16
+ end
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pgdiff
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Charlie Savage
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pg
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.17.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.17.0
27
+ description: |
28
+ Compares two PostgreSQL databases and generates the SQL statements needed to make their structure the same.
29
+ email:
30
+ executables:
31
+ - pgdiff
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - CHANGELOG.rdoc
36
+ - README.rdoc
37
+ - Rakefile
38
+ - bin/pgdiff
39
+ - lib/attribute.rb
40
+ - lib/database.rb
41
+ - lib/diff.rb
42
+ - lib/function.rb
43
+ - lib/pgdiff.rb
44
+ - lib/rule.rb
45
+ - lib/sequence.rb
46
+ - lib/table.rb
47
+ - lib/trigger.rb
48
+ - lib/view.rb
49
+ homepage: https://github.com/cfis/pgdiff.git
50
+ licenses:
51
+ - MIT
52
+ metadata: {}
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 1.9.3
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 2.4.5
70
+ signing_key:
71
+ specification_version: 4
72
+ summary: Provides a ruby script that compares two PostgreSQL databases and generates
73
+ the SQL statements needed to make their structure the same. The original version
74
+ was posted at http://www.dzone.com/snippets/pgdiff-compare-two-postgresql.
75
+ test_files: []