pgdiff 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []