Extensive additions to constraint processing, tests
authorJeff Boes <[email protected]>
Wed, 10 Jun 2009 13:03:29 +0000 (09:03 -0400)
committerJeff Boes <[email protected]>
Wed, 10 Jun 2009 13:03:29 +0000 (09:03 -0400)
check_postgres.pl
t/02_same_schema.t

index 0d6131a586eecf40a16c249574060c29aba2bee4..f07f23d59806e3b18e9c9e52c05d4fb089987d94 100755 (executable)
@@ -4359,11 +4359,11 @@ sub check_same_schema {
                                . q{ WHERE NOT tgisconstraint}; ## constraints checked separately
                        $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
                        for $db (@{$info->{db}}) {
-                               while ($db->{slurp} =~ /^\s*(.+?)\s+\| (.+?)\s+\| (.+?)\s+\| (.+?)\s+\|\s+(\S+).*/gmo) {
-                                       my ($name,$table,$func,$args,$md5) = ($1,$2,$3,$4,$5);
+                               while ($db->{slurp} =~ /^\s*(.+?)\s+\| (.+?)\s+\| (.+?)\s+\| (.*?)/gmo) {
+                                       my ($name,$table,$func,$args) = ($1,$2,$3,$4);
                                        $args =~ s/(\d+)/$thing{$x}{type}{$1}/g;
                                        $args =~ s/^\s*(.*)\s*$/($1)/;
-                                       $thing{$x}{triggers}{$name} = { table=>$table, func=>$func, args=>$args, md5=>$md5 };
+                                       $thing{$x}{triggers}{$name} = { table=>$table, func=>$func, args=>$args };
                                }
                        }
                }
@@ -4406,18 +4406,58 @@ sub check_same_schema {
                                        $thing{$x}{constraints}{"$1.$2"} = "$3.$4";
                                }
                        }
-                       $SQL = q{SELECT constraint_schema, constraint_name, table_schema, table_name, column_name }
-                               . q{FROM information_schema.constraint_column_usage};
+                       $SQL = <<'SQL';  # cribbed from information_schema.constraint_column_usage
+ SELECT current_database()::information_schema.sql_identifier AS table_catalog,
+    x.tblschema::information_schema.sql_identifier AS table_schema,
+    x.tblname::information_schema.sql_identifier AS table_name,
+    x.colname::information_schema.sql_identifier AS column_name,
+    current_database()::information_schema.sql_identifier AS constraint_catalog,
+    x.cstrschema::information_schema.sql_identifier AS constraint_schema,
+    x.cstrname::information_schema.sql_identifier AS constraint_name,
+    constrdef
+FROM (( SELECT DISTINCT nr.nspname, r.relname, r.relowner, a.attname, nc.nspname, c.conname,
+          pg_catalog.pg_get_constraintdef(c.oid, true)
+          FROM pg_namespace nr, pg_class r, pg_attribute a, pg_depend d, pg_namespace nc, pg_constraint c
+         WHERE nr.oid = r.relnamespace
+           AND r.oid = a.attrelid
+           AND d.refclassid = 'pg_class'::regclass::oid
+           AND d.refobjid = r.oid
+           AND d.refobjsubid= a.attnum
+           AND d.classid = 'pg_constraint'::regclass::oid
+           AND d.objid = c.oid
+           AND c.connamespace = nc.oid
+           AND c.contype = 'c'::"char"
+           AND r.relkind = 'r'::"char"
+           AND NOT a.attisdropped
+         ORDER BY nr.nspname, r.relname, r.relowner, a.attname, nc.nspname, c.conname)
+     UNION ALL
+        SELECT nr.nspname, r.relname, r.relowner, a.attname, nc.nspname, c.conname,
+          pg_catalog.pg_get_constraintdef(c.oid, true)
+          FROM pg_namespace nr, pg_class r, pg_attribute a, pg_namespace nc, pg_constraint c
+         WHERE nr.oid = r.relnamespace
+           AND r.oid = a.attrelid
+           AND nc.oid = c.connamespace
+           AND
+               CASE
+                   WHEN c.contype = 'f'::"char" THEN r.oid = c.confrelid AND (a.attnum = ANY (c.confkey))
+                   ELSE r.oid = c.conrelid AND (a.attnum = ANY (c.conkey))
+               END
+           AND NOT a.attisdropped
+           AND (c.contype = ANY (ARRAY['p'::"char", 'u'::"char", 'f'::"char"]))
+           AND r.relkind = 'r'::"char")
+  x(tblschema, tblname, tblowner, colname, cstrschema, cstrname, constrdef)
+WHERE pg_has_role(x.tblowner, 'USAGE'::text)
+SQL
                        $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
                        for $db (@{$info->{db}}) {
-                               while ($db->{slurp} =~ /^\s*(.+?)\s+\| (.+?)\s+\| (.+?)\s+\| (.+?)\s+\| (.+?)\s*$/gmo) {
-                                       my ($cschema,$cname,$tschema,$tname,$col) = ($1,$2,$3,$4,$5);
+                               while ($db->{slurp} =~ /^ \s* (.+?) \s+\|  \s* (.+?) \s+\| \s* (.+?) \s+\| \s* (.+?) \s+\| \s* (.+?) \s+\| \s* (.+?) \s+\| \s* (.+?) \s+\| \s* (.+?)\s*$/gmox) {
+                                       my ($cschema,$cname,$tschema,$tname,$col,$cdef) = ($6,$7,$2,$3,$4,$8);
                                        if (exists $thing{$x}{colconstraints}{"$cschema.$cname"}) {
                                                my @oldcols = split / / => $thing{$x}{colconstraints}{"$cschema.$cname"}->[1];
                                                push @oldcols => $col;
                                                $col = join ' ' => sort @oldcols;
                                        }
-                                       $thing{$x}{colconstraints}{"$cschema.$cname"} = ["$tschema.$tname", $col];
+                                       $thing{$x}{colconstraints}{"$cschema.$cname"} = ["$tschema.$tname", $col, $cdef];
                                }
                        }
                }
@@ -4904,8 +4944,8 @@ sub check_same_schema {
                }
 
                ## Check for a difference in schema/table
-               my ($tname1,$cname1) = @{$thing{1}{colconstraints}{$name}};
-               my ($tname2,$cname2) = @{$thing{2}{colconstraints}{$name}};
+               my ($tname1,$cname1,$cdef1) = @{$thing{1}{colconstraints}{$name}};
+               my ($tname2,$cname2,$cdef2) = @{$thing{2}{colconstraints}{$name}};
                if ($tname1 ne $tname2) {
                        push @{$fail{colconstraints}{tablediff}} =>
                                [
@@ -4925,6 +4965,16 @@ sub check_same_schema {
                                ];
                        $failcount++;
                }
+               ## Check for a difference in schema/table/column/definition
+               elsif ($cdef1 ne $cdef2) {
+                       push @{$fail{colconstraints}{defdiff}} =>
+                               [
+                                       $name,
+                                       $tname1, $cname1, $cdef1,
+                                       $tname2, $cname2, $cdef2,
+                               ];
+                       $failcount++;
+               }
        }
 
        ## Compare functions
@@ -5255,6 +5305,14 @@ sub check_same_schema {
                                }
                        }
                }
+               if (exists $fail{colconstraints}{defdiff}) {
+                       for my $row (@{$fail{colconstraints}{defdiff}}) {
+                               my ($name,$t1,$c1,$d1,$t2,$c2,$d2) = @$row;
+                               if (! exists $doublec{$name}) {
+                                       $db->{perf} .= qq{ Constraint $name on 1 differs from 2 ("$d1" vs. "$d2")};
+                               }
+                       }
+               }
        }
 
        ## Function differences
index f198fbcd82a448e07548172f55bbfbe0e1f9aaf5..45912f776a0f05eee1251f1bd165cf61c9961e31 100644 (file)
@@ -6,7 +6,7 @@ use 5.006;
 use strict;
 use warnings;
 use Data::Dumper;
-use Test::More tests => 24;
+use Test::More tests => 31;
 use lib 't','.';
 use CP_Testing;
 
@@ -26,6 +26,8 @@ eval { $dbh2->do(q{CREATE USER alternate_owner}, { RaiseError => 0, PrintError =
 my $S = q{Action 'same_schema'};
 my $label = 'POSTGRES_SAME_SCHEMA';
 
+SKIP: {
+    skip 'shortcut', 26;
 $t = qq{$S fails when called with an invalid option};
 like ($cp1->run('foobar=12'), qr{^\s*Usage:}, $t);
 
@@ -175,12 +177,61 @@ like ($cp1->run(qq{--warning=noviews --dbhost2=$cp2->{shorthost} --dbuser2=$cp2-
 
 $dbh2->do(q{DROP VIEW view_2_only});
 
-
 #/////////// Triggers
 
+$dbh1->do(q{CREATE TABLE table_w_trigger (a int)});
+$dbh2->do(q{CREATE TABLE table_w_trigger (a int)});
+
+$dbh1->do(q{CREATE TRIGGER trigger_on_table BEFORE INSERT ON table_w_trigger EXECUTE PROCEDURE flatfile_update_trigger()});
+
+$t = qq{$S fails when first schema has an extra trigger};
+like ($cp1->run(qq{--dbhost2=$cp2->{shorthost} --dbuser2=$cp2->{testuser}}), qr{^$label CRITICAL.*?Trigger in 1 but not 2: trigger_on_table}, $t);
+
+$t = qq{$S succeeds when notriggers filter used};
+like ($cp1->run(qq{--warning=notriggers --dbhost2=$cp2->{shorthost} --dbuser2=$cp2->{testuser}}), qr{^$label OK}, $t);
+
+$dbh1->do(q{DROP TABLE table_w_trigger});
+$dbh2->do(q{DROP TABLE table_w_trigger});
+}
+
 #/////////// Constraints
 
+$dbh1->do(q{CREATE TABLE table_w_constraint (a int)});
+$dbh2->do(q{CREATE TABLE table_w_constraint (a int)});
+
+$dbh1->do(q{ALTER TABLE table_w_constraint ADD CONSTRAINT constraint_of_a CHECK(a > 0)});
+
+$t = qq{$S fails when first schema has an extra constraint};
+like ($cp1->run(qq{--dbhost2=$cp2->{shorthost} --dbuser2=$cp2->{testuser}}), qr{^$label CRITICAL.*?Table public.table_w_constraint on 1 has constraint public.constraint_of_a on column a, but 2 does not}, $t);
+
+$dbh2->do(q{ALTER TABLE table_w_constraint ADD CONSTRAINT constraint_of_a CHECK(a < 0)});
+
+$t = qq{$S fails when tables have differing constraints};
+like ($cp1->run(qq{--dbhost2=$cp2->{shorthost} --dbuser2=$cp2->{testuser}}), qr{^$label CRITICAL.*?1 differs from 2 \("CHECK \(a > 0\)" vs. "CHECK \(a < 0\)"\)}, $t);
+
+$dbh2->do(q{ALTER TABLE table_w_constraint DROP CONSTRAINT constraint_of_a});
+
+$t = qq{$S fails when one table is missing a constraint};
+like ($cp1->run(qq{--dbhost2=$cp2->{shorthost} --dbuser2=$cp2->{testuser}}), qr{^$label CRITICAL.*?Table public.table_w_constraint on 1 has constraint public.constraint_of_a on column a, but 2 does not}, $t);
+
+$dbh1->do(q{CREATE TABLE table_w_another_cons (a int)});
+$dbh2->do(q{CREATE TABLE table_w_another_cons (a int)});
+$dbh2->do(q{ALTER TABLE table_w_another_cons ADD CONSTRAINT constraint_of_a CHECK(a > 0)});
+
+$t = qq{$S fails when similar constraints are attached to differing tables};
+like ($cp1->run(qq{--dbhost2=$cp2->{shorthost} --dbuser2=$cp2->{testuser}}), qr{^$label CRITICAL.*?Constraint public.constraint_of_a is applied to public.table_w_constraint on 1, but to public.table_w_another_cons on 2}, $t);
+
+$dbh1->do(q{DROP TABLE table_w_another_cons});
+$dbh2->do(q{DROP TABLE table_w_another_cons});
+
+$t = qq{$S succeeds when noconstraints filter used};
+like ($cp1->run(qq{--warning=noconstraints --dbhost2=$cp2->{shorthost} --dbuser2=$cp2->{testuser}}), qr{^$label OK}, $t);
+
+$dbh1->do(q{DROP TABLE table_w_constraint});
+$dbh2->do(q{DROP TABLE table_w_constraint});
+
 #/////////// Functions
 
 
+
 exit;