. 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 };
}
}
}
$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];
}
}
}
}
## 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}} =>
[
];
$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
}
}
}
+ 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
use strict;
use warnings;
use Data::Dumper;
-use Test::More tests => 24;
+use Test::More tests => 31;
use lib 't','.';
use CP_Testing;
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);
$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;