|
From: <ssk...@re...> - 2004-11-17 16:31:24
|
Author: sskracic
Date: 2004-11-17 17:23:56 +0100 (Wed, 17 Nov 2004)
New Revision: 107
Added:
users/sskracic/
users/sskracic/bin/
users/sskracic/bin/pgcompare.tcl
Log:
Tcl utility that compares the metadata of two Postgres databases.
Added: users/sskracic/bin/pgcompare.tcl
===================================================================
--- users/sskracic/bin/pgcompare.tcl 2004-11-17 10:56:55 UTC (rev 106)
+++ users/sskracic/bin/pgcompare.tcl 2004-11-17 16:23:56 UTC (rev 107)
@@ -0,0 +1,231 @@
+#! /usr/bin/tclsh
+
+proc has_option {opt} {
+ upvar options options
+ set yield [expr {[lsearch -exact $options $opt] > -1}]
+ return $yield
+}
+
+set usage "Usage: $argv0 \[options\] db1 db2
+ Options:
+
+ -t <type> limit comparison scope to objects of <type>
+ only. Possible values for <type>:
+ sequence, function, view, table,
+ constraint, trigger, index
+ -n report only object names, w/o definition
+ -1 suppress objects unique to db1
+ -2 suppress objects unique to db2
+ -12 suppress objects contained in both databases
+ -c append contraint definitions to tables
+ -i append index definitions to tables
+ -s shows identical objects only (implies -1 -2, ignores -12)
+ -d shows different objects only (implies -1 -2, ignores -12)
+ -h -? shows this help text
+ -- end of options
+
+ Multiple options can be specified, although this doesn't
+ make sense sometimes. The default is -1 -2.
+"
+
+
+set err [list]
+
+set filter "*"
+set options [list]
+for {set optionc 0} {$optionc < [llength $argv]} {incr optionc} {
+ set arg [lindex $argv $optionc]
+ if {[regexp -- {-(.*)} $arg all opt]} {
+ switch -exact -- $opt {
+ "-" { break }
+ "t" { set filter [lindex $argv [incr optionc]] ; lappend options $opt }
+ "n" -
+ "1" -
+ "2" -
+ "12" -
+ "c" -
+ "i" -
+ "s" -
+ "h" -
+ "?" -
+ "d" { lappend options $opt }
+ default { lappend err "Invalid option: -$opt" }
+ }
+ } else {
+ break
+ }
+}
+
+if {[has_option h] || [has_option ?]} {
+ puts $usage
+ exit 2
+}
+
+if {[expr $optionc + 2] != [llength $argv]} {
+ lappend err "Exactly 2 database names must be specified"
+}
+
+if {[llength $err]} {
+ puts [join $err "\n"]
+ puts $usage
+ exit 1
+}
+
+set db1 [lindex $argv $optionc]
+set db2 [lindex $argv [expr $optionc + 1]]
+
+if {[has_option d] || [has_option s]} {
+ lappend options 1 2
+ # remove 12
+ set options [lsearch -all -inline -not $options 12]
+}
+
+if {[lsearch $options {[12]}] == -1} {
+ lappend options 1 2
+}
+
+
+# Now the only thing left to do is some crazy regexp matching
+
+# For each object type we want to compare, we specify a regexp which
+# should hopefully grab the object definition from the dump file
+
+set types(view) {--\n\n(CREATE VIEW (\S+?) .*?;)()\n}
+set types(function) {--\n\n(CREATE FUNCTION (\S+?) .*?LANGUAGE (?:c|plpgsql);)()\n}
+set types(table) {--\n\n(CREATE TABLE ((\S+?)) .*?;)\n}
+set types(sequence) {--\n\n(CREATE SEQUENCE (\S+?)\n.*?;)()\n}
+set types(constraint) {--\n\n(ALTER TABLE ONLY (\S+?)\n *?ADD CONSTRAINT (\S+?) .*?;)\n}
+set types(trigger) {--\n\n(CREATE TRIGGER (\S+?)\n *?(?:AFTER|BEFORE) (?:INSERT|DELETE|UPDATE) ON (\S+?)\n.*?;)\n}
+set types(index) {--\n\n(CREATE (?:UNIQUE )??INDEX (\S+?) ON (\S+?) .*?;)\n}
+
+# For each object in the catalog, we will make an entry in objects array.
+# The key of the entry will look like $object_type:$table_name:$object_name,
+# and the value will be object definition. Note that $table_name only
+# has meaning for constraints, triggers, indices and (redundantly) tables.
+# For views, functions and sequences this part is empty.
+
+foreach dbindex {db1 db2} {
+
+ set dbname [set $dbindex]
+
+ puts "Obtaining schema for $dbname"
+ set dbschema [exec pg_dump -s $dbname]
+
+ foreach type [array names types -glob $filter] {
+ puts " Fetching definition for object type: $type"
+ set rgxp $types($type)
+ set matches [regexp -inline -all -- $rgxp $dbschema]
+ # We must not forget that each regexp match will produce 4 elements in the $matches list.
+ # First element is complete match, while second is the definition.
+ # The third one is object name, except for the constraints where the table name comes first.
+ for {set i 0} {$i < [llength $matches]} {incr i 4} {
+ set definition [lindex $matches [expr $i + 1]]
+ if {"$type" == "constraint"} {
+ set table_name [lindex $matches [expr $i + 2]]
+ set obj_name [lindex $matches [expr $i + 3]]
+ } else {
+ set obj_name [lindex $matches [expr $i + 2]]
+ set table_name [lindex $matches [expr $i + 3]]
+ }
+ set key "$type:$table_name:$obj_name"
+ set objects[set dbindex]($key) $definition
+ }
+ }
+
+ if {[has_option c]} {
+ # Now we will append constraint definitions to tables, sorted by constraint name
+ foreach constraint_key [lsort [array names objects$dbindex {constraint:*}]] {
+ set keylist [split $constraint_key :]
+ set tablename [lindex $keylist 1]
+ set table_key "table:$tablename:$tablename"
+ append objects[set dbindex]($table_key) "\n[set objects[set dbindex]($constraint_key)]"
+ # remove this constraint from object listing
+ array unset objects$dbindex $constraint_key
+ }
+ }
+
+ if {[has_option i]} {
+ # Now we will append index definitions to tables
+ foreach index_key [lsort [array names objects$dbindex {index:*}]] {
+ set keylist [split $index_key :]
+ set tablename [lindex $keylist 1]
+ set table_key "table:$tablename:$tablename"
+ append objects[set dbindex]($table_key) "\n[set objects[set dbindex]($index_key)]"
+ # remove this index from object listing
+ array unset objects$dbindex $index_key
+ }
+ }
+
+}
+
+set onlyindb1 [list]
+set onlyindb2 [list]
+set inboth [list]
+
+set indb1 [array names objectsdb1]
+set indb2 [array names objectsdb2]
+
+foreach entry [lsort -unique [concat $indb1 $indb2]] {
+ set in1 [lsearch $indb1 $entry]
+ set in2 [lsearch $indb2 $entry]
+ if {$in1 >= 0 && $in2 >= 0} {
+ lappend inboth $entry
+ } elseif {$in1 >= 0} {
+ lappend onlyindb1 $entry
+ } elseif {$in2 >= 0} {
+ lappend onlyindb2 $entry
+ }
+}
+
+puts "Number of objects appearing in both databases: [llength $inboth]"
+puts "Number of objects unique to $db1: [llength $onlyindb1]"
+puts "Number of objects unique to $db2: [llength $onlyindb2]"
+
+if {![has_option 1]} {
+ foreach entry $onlyindb1 {
+ puts "ONLY IN $db1: $entry"
+ if {![has_option n]} {
+ puts "$objectsdb1($entry)"
+ puts "---"
+ }
+ }
+}
+
+if {![has_option 2]} {
+ foreach entry $onlyindb2 {
+ puts "ONLY IN $db2: $entry"
+ if {![has_option n]} {
+ puts "$objectsdb2($entry)"
+ puts "---"
+ }
+ }
+}
+
+if {![has_option 12]} {
+ foreach entry $inboth {
+ # Now compare two
+ set definition1 [string map {" " ""} $objectsdb1($entry)]
+ set definition2 [string map {" " ""} $objectsdb2($entry)]
+ if {[string equal -nocase $definition1 $definition2]} {
+ if {![has_option d]} {
+ puts "IDENTICAL: $entry"
+ if {![has_option n]} {
+ puts "$objectsdb1($entry)"
+ puts "---"
+ }
+ }
+ } else {
+ if {![has_option s]} {
+ puts "DIFFERENCE: $entry"
+ if {![has_option n]} {
+ puts "$db1: $objectsdb1($entry)"
+ puts "$db2: $objectsdb2($entry)"
+ puts "---"
+ }
+ }
+ }
+ }
+}
+
+
+
Property changes on: users/sskracic/bin/pgcompare.tcl
___________________________________________________________________
Name: svn:executable
+ *
|