summaryrefslogtreecommitdiff
path: root/check_postgres.pl
diff options
context:
space:
mode:
Diffstat (limited to 'check_postgres.pl')
-rwxr-xr-xcheck_postgres.pl386
1 files changed, 114 insertions, 272 deletions
diff --git a/check_postgres.pl b/check_postgres.pl
index 87eacc1b5..2a8e9bf8a 100755
--- a/check_postgres.pl
+++ b/check_postgres.pl
@@ -4955,7 +4955,7 @@ WHERE nspname !~ '^pg_t'
}
## Get a list of all relations
- if (! exists $filter{notables}) {
+ if (! exists $filter{notables} or !exists $filter{noconstraints}) {
$SQL = q{
SELECT relkind, quote_ident(nspname) AS nspname, quote_ident(relname) AS relname,
quote_ident(usename) AS usename, relacl,
@@ -5077,76 +5077,20 @@ ORDER BY table_schema, table_name, ordinal_position, column_name
## We'll use information_schema for this one too
if (! exists $filter{noconstraints}) {
$SQL = q{
-SELECT constraint_schema AS cs, constraint_name AS cn, table_schema AS ts, table_name AS tn
-FROM information_schema.constraint_table_usage
+SELECT n1.nspname AS cschema, conname, contype, n1.nspname AS tschema, relname AS tname, conkey, consrc
+FROM pg_constraint c
+JOIN pg_namespace n1 ON (n1.oid = c.connamespace)
+JOIN pg_class r ON (r.oid = c.conrelid)
+JOIN pg_namespace n2 ON (n2.oid = r.relnamespace)
+WHERE n1.nspname !~ 'pg_'
};
- $info = run_command($SQL, { dbuser => $opt{dbuser}[$x-1], dbnumber => $x } );
- for $db (@{$info->{db}}) {
- for my $r (@{$db->{slurp}}) {
- my ($ichi,$ni,$san,$shi) = @$r{qw/ cs cn ts tn/};
-
- ## No sense in grabbing "generic" constraints
- next if $ni =~ /^\$\d+$/o;
- $thing{$x}{constraints}{"$ichi.$ni"} = "$san.$shi";
- }
- }
- $SQL = <<'SQL'; # cribbed from information_schema.constraint_column_usage
-SELECT current_database()::information_schema.sql_identifier AS cd,
- x.tblschema::information_schema.sql_identifier AS tschema,
- x.tblname::information_schema.sql_identifier AS tname,
- x.colname::information_schema.sql_identifier AS ccol,
- current_database()::information_schema.sql_identifier AS constraint_catalog,
- x.cstrschema::information_schema.sql_identifier AS cschema,
- x.cstrname::information_schema.sql_identifier AS cname,
- REGEXP_REPLACE(constrdef, '\n', ' \\n ','g') AS cdef
-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}}) {
for my $r (@{$db->{slurp}}) {
- my ($cschema,$cname,$tschema,$tname,$ccol,$cdef) = @$r{
- qw/cschema cname tschema tname ccol cdef/};
- ## No sense in grabbing "generic" constraints
- if ($cname !~ /^\$\d+$/o) {
- if (exists $thing{$x}{colconstraints}{"$cschema.$cname"}) {
- my @oldcols = split / / => $thing{$x}{colconstraints}{"$cschema.$cname"}->[1];
- push @oldcols => $ccol;
- $ccol = join ' ' => sort @oldcols;
- }
- $thing{$x}{colconstraints}{"$cschema.$cname"} = ["$tschema.$tname", $ccol, $cdef];
- }
+ my ($cs,$name,$type,$ts,$tn,$key,$src) =
+ @$r{qw/ cschema conname contype tschema tname conkey consrc/};
+ $thing{$x}{constraints}{"$ts.$tn"}{$name} = [$type,$key,$src];
}
}
}
@@ -5306,6 +5250,9 @@ JOIN pg_namespace n ON (n.oid = pronamespace)
## We treat the name as a unified "schema.relname"
TABLE1:
for my $name (sort keys %{$thing{1}{tables}}) {
+
+ next if exists $filter{notables};
+
next if exists $thing{2}{tables}{$name};
## If the schema does not exist, don't bother reporting it
@@ -5329,6 +5276,8 @@ JOIN pg_namespace n ON (n.oid = pronamespace)
TABLE2:
for my $name (sort keys %{$thing{2}{tables}}) {
+ next if exists $filter{notables};
+
if (exists $filter{notable_regex}) {
for my $regex (@{$filter{notable_regex}}) {
next TABLE2 if $name =~ /$regex/;
@@ -5627,139 +5576,95 @@ JOIN pg_namespace n ON (n.oid = pronamespace)
## Compare constraints
- ## Table constraints - any exists on 1 but not 2?
- CONSTRAINT1:
- for my $name (sort keys %{$thing{1}{constraints}}) {
- next if exists $thing{2}{constraints}{$name};
-
- ## If the table does not exist, we don't report it
- next if ! exists $thing{2}{tables}{ $thing{1}{constraints}{$name} };
-
- if (exists $filter{noconstraint_regex}) {
- for my $regex (@{$filter{noconstraint_regex}}) {
- next CONSTRAINT1 if $name =~ /$regex/;
- }
- }
+ ## Constraints - any exists on 1 but not 2?
+ for my $tname (sort keys %{$thing{1}{constraints}}) {
- for my $exclude (@{$opt{exclude}}) {
- next CONSTRAINT1 if $name =~ /$exclude/;
- }
+ ## If the table does not exist, no sense in going on
+ next if ! exists $thing{2}{tables}{$tname};
- push @{$fail{constraints}{notexist}{1}} => [$name, $thing{1}{constraints}{$name}];
- $failcount++;
- }
+ C11: for my $cname (sort keys %{$thing{1}{constraints}{$tname}}) {
- ## Check exists on 2 but not 1, and make sure the schema/table matches
- CONSTRAINT2:
- for my $name (sort keys %{$thing{2}{constraints}}) {
+ ## Move on if it exists on 2
+ next if exists $thing{2}{constraints}{$tname}{$cname};
- if (exists $filter{noconstraint_regex}) {
- for my $regex (@{$filter{noconstraint_regex}}) {
- next CONSTRAINT2 if $name =~ /$regex/;
+ if (exists $filter{noconstraint_regex}) {
+ for my $regex (@{$filter{noconstraint_regex}}) {
+ next C11 if $cname =~ /$regex/;
+ }
}
- }
-
- for my $exclude (@{$opt{exclude}}) {
- next CONSTRAINT2 if $name =~ /$exclude/;
- }
- if (! exists $thing{1}{constraints}{$name}) {
-
- ## If the table does not exist, we don't report it
- if (exists $thing{1}{tables}{ $thing{2}{constraints}{$name} }) {
- push @{$fail{constraints}{notexist}{2}} => [$name, $thing{2}{constraints}{$name}];
- $failcount++;
+ for my $exclude (@{$opt{exclude}}) {
+ next C11 if $cname =~ /$exclude/;
}
- next;
- }
- if ($thing{1}{constraints}{$name} ne $thing{2}{constraints}{$name}) {
- push @{$fail{constraints}{tablediff}} =>
- [
- $name,
- $thing{1}{constraints}{$name},
- $thing{2}{constraints}{$name},
- ];
+ push @{$fail{constraints}{notexist}{1}} => [$cname, $tname];
$failcount++;
}
}
- ## Column constraints - any exists on 1 but not 2?
- CONSTRAINT3:
- for my $name (sort keys %{$thing{1}{colconstraints}}) {
- next if exists $thing{2}{colconstraints}{$name};
+ ## Check for constraints that exist on 2 but not 1
+ ## Also dig in and compare ones that do match
+ for my $tname (sort keys %{$thing{2}{constraints}}) {
- ## If the table does not exist, we don't report it
- my ($tname,$cname) = @{$thing{1}{colconstraints}{$name}};
- next if ! exists $thing{2}{tables}{$tname};
+ ## If the table does not exist, no sense in going on
+ next if ! exists $thing{1}{tables}{$tname};
+
+ C22: for my $cname (sort keys %{$thing{2}{constraints}{$tname}}) {
- if (exists $filter{noconstraint_regex}) {
- for my $regex (@{$filter{noconstraint_regex}}) {
- next CONSTRAINT3 if $name =~ /$regex/;
+ if (exists $filter{noconstraint_regex}) {
+ for my $regex (@{$filter{noconstraint_regex}}) {
+ next C22 if $cname =~ /$regex/;
+ }
}
- }
- for my $exclude (@{$opt{exclude}}) {
- next CONSTRAINT3 if $name =~ /$exclude/;
- }
+ for my $exclude (@{$opt{exclude}}) {
+ next C22 if $cname =~ /$exclude/;
+ }
- push @{$fail{colconstraints}{notexist}{1}} => [$name, $tname, $cname];
- $failcount++;
- }
+ if (! exists $thing{1}{constraints}{$tname}{$cname}) {
+ push @{$fail{constraints}{notexist}{2}} => [$cname, $tname];
+ $failcount++;
+ next C22;
+ }
- ## Check exists on 2 but not 1, and make sure the schema/table/column matches
- CONSTRAINT4:
- for my $name (sort keys %{$thing{2}{colconstraints}}) {
+ my ($type1,$key1,$cdef1) = @{$thing{1}{constraints}{$tname}{$cname}};
+ my ($type2,$key2,$cdef2) = @{$thing{2}{constraints}{$tname}{$cname}};
- if (exists $filter{noconstraint_regex}) {
- for my $regex (@{$filter{noconstraint_regex}}) {
- next CONSTRAINT4 if $name =~ /$regex/;
+ ## Are they the same type?
+ if ($type1 ne $type2) {
+ push @{$fail{constraints}{difftype}} => [$cname, $tname, $type1, $type2];
+ $failcount++;
+ next C22;
}
- }
- for my $exclude (@{$opt{exclude}}) {
- next CONSTRAINT4 if $name =~ /$exclude/;
- }
+ ## Are they on the same key?
+ if ($key1 ne $key2) {
+ push @{$fail{constraints}{diffkey}} => [$cname, $tname, $key1, $key2];
+ $failcount++;
+ ## Fall through and possible check the source as well
+ }
- if (! exists $thing{1}{colconstraints}{$name}) {
+ ## Only bother with the source for check constraints
+ next C22 if $type1 ne 'c';
- ## If the table does not exist, we don't report it
- my ($tname,$cname) = @{$thing{2}{colconstraints}{$name}};
- if (exists $thing{1}{tables}{ $tname }) {
- push @{$fail{colconstraints}{notexist}{2}} => [$name, $tname, $cname];
- $failcount++;
+ ## Is the source the same?
+ if ($cdef1 eq $cdef2) {
+ next C22;
+ }
+
+ ## It may be because 8.2 and earlier over-quoted things
+ ## Just in case, we'll compare sans double quotes
+ (my $cdef11 = $cdef1) =~ s/"//g;
+ (my $cdef22 = $cdef2) =~ s/"//g;
+ if ($cdef11 eq $cdef22) {
+ $VERBOSE >= 1 and warn "Constraint $cname on $tname matched when quotes were removed\n";
+ next C22;
}
- next;
- }
- ## Check for a difference in schema/table
- my ($tname1,$cname1,$cdef1) = @{$thing{1}{colconstraints}{$name}};
- my ($tname2,$cname2,$cdef2) = @{$thing{2}{colconstraints}{$name}};
- if ($tname1 ne $tname2) {
- push @{$fail{colconstraints}{tablediff}} =>
- [
- $name,
- $tname1,
- $tname2,
- ];
- $failcount++;
- }
- ## Check for a difference in schema/table/column
- elsif ($cname1 ne $cname2) {
- push @{$fail{colconstraints}{columndiff}} =>
- [
- $name,
- $tname1, $cname1,
- $tname2, $cname2,
- ];
- $failcount++;
- }
- ## Check for a difference in schema/table/column/definition
- elsif ($cdef1 ne $cdef2) {
## Constraints are written very differently according to the Postgres version
## We'll try to do some normalizing here
my $var = qr{(?:''|'?\w+[\w ]*'?)(?:::\w[\w ]+\w+)?};
- my $equiv = qr{$var = $var};
+ my $equiv = qr{$var (?:=|>=|<=) $var};
## Change double cast using parens to three cast form
my %dtype = (
@@ -5771,6 +5676,7 @@ JOIN pg_namespace n ON (n.oid = pronamespace)
my $dtype = join '|' => keys %dtype;
for my $s1 ($cdef1, $cdef2) {
+
## Remove parens about left side of cast: (foo)::bar => foo::bar
$s1 =~ s/\((\w+)\)::(\w+)/${1}::$2/g;
@@ -5801,8 +5707,12 @@ JOIN pg_namespace n ON (n.oid = pronamespace)
$flat ? $extraparen ? "$flat)" : $flat : $all;
}ge;
+ ## Strip left to right three part casting parens
+ ## (foo::text)::integer => foo::text::integer
+ $s1 =~ s{\((\w[\w ]*?::\w[\w ]*?)\)(::\w[\w ]*\w* )}{$1$2}g;
+
## Get rid of excess parens in OR clauses
- $s1 =~ s{\(($equiv(?: OR $equiv)+)\)}{$1}g;
+ 1 while $s1 =~ s{\(($equiv(?: OR $equiv)+)\)}{$1};
## Remove parens around entire thing
$s1 =~ s{^\s*\((.+)\)\s*$}{$1};
@@ -5811,27 +5721,16 @@ JOIN pg_namespace n ON (n.oid = pronamespace)
$s1 =~ s{^\s*CHECK \((.+)\)\s*$}{CHECK $1};
$s1 =~ s{($dtype)\((\w+)::($dtype)\)}{$2::$3::$dtype{$1}}g;
- }
+
+ } ## end of normalizing
+
if ($cdef1 ne $cdef2) {
- ## It may be because 8.2 and earlier over-quoted things
- ## Just in case, we'll compare sans double quotes
- (my $cdef11 = $cdef1) =~ s/"//g;
- (my $cdef22 = $cdef2) =~ s/"//g;
- if ($cdef11 eq $cdef22) {
- $VERBOSE >= 1 and warn "Constraint $cname1 on $tname1 matched when quotes were removed\n";
- }
- else {
- push @{$fail{colconstraints}{defdiff}} =>
- [
- $name,
- $tname1, $cname1, $cdef1,
- $tname2, $cname2, $cdef2,
- ];
- $failcount++;
- }
+ push @{$fail{constraints}{diffsrc}} => [$cname, $tname, $cdef1, $cdef2];
+ $failcount++;
}
- }
- }
+
+ } ## end each constraint on this table
+ } ## end each table
## Compare languages
for my $name (sort keys %{$thing{1}{language}}) {
@@ -6162,93 +6061,36 @@ JOIN pg_namespace n ON (n.oid = pronamespace)
}
}
- ## Constraint differences - table level
- ## Don't report things twice
- my %doublec;
+ ## Constraint differences
if (exists $fail{constraints}) {
- if (exists $fail{constraints}{notexist}) {
- if (exists $fail{constraints}{notexist}{1}) {
- for my $row (@{$fail{constraints}{notexist}{1}}) {
- my ($cname,$tname) = @$row;
- $db->{perf} .= qq{ Table "$tname" on 1 has constraint "$cname", but 2 does not. };
- $doublec{$cname}++;
- }
- }
- if (exists $fail{constraints}{notexist}{2}) {
- for my $row (@{$fail{constraints}{notexist}{2}}) {
- my ($cname,$tname) = @$row;
- $db->{perf} .= qq{ Table "$tname" on 2 has constraint "$cname", but 1 does not. };
- $doublec{$cname}++;
- }
- }
+
+ ## Exists on 1 but not 2
+ for my $row (@{$fail{constraints}{notexist}{1}}) {
+ my ($cname,$tname) = @$row;
+ $db->{perf} .= qq{ Table "$tname" on 1 has constraint "$cname", but 2 does not. };
}
- if (exists $fail{constraints}{tablediff}) {
- for my $row (@{$fail{constraints}{tablediff}}) {
- my ($cname,$t1,$t2) = @$row;
- $db->{perf} .= qq{ Constraint "$cname" is applied to "$t1" on 1, but to "$t2" on 2. };
- $doublec{$cname}++;
- }
+ ## Exists on 2 but not 1
+ for my $row (@{$fail{constraints}{notexist}{2}}) {
+ my ($cname,$tname) = @$row;
+ $db->{perf} .= qq{ Table "$tname" on 2 has constraint "$cname", but 1 does not. };
}
- }
- ## Constraint differences - column level
- if (exists $fail{colconstraints}) {
- if (exists $fail{colconstraints}{notexist}) {
- if (exists $fail{colconstraints}{notexist}{1}) {
- for my $row (@{$fail{colconstraints}{notexist}{1}}) {
- my ($name,$tname,$cname) = @$row;
- if (! exists $doublec{$name}) {
- $db->{perf} .= qq{ Table "$tname" on 1 has constraint "$name" on column "$cname", but 2 does not. };
- }
- else {
- $failcount--;
- }
- }
- }
- if (exists $fail{colconstraints}{notexist}{2}) {
- for my $row (@{$fail{colconstraints}{notexist}{2}}) {
- my ($name,$tname,$cname) = @$row;
- if (! exists $doublec{$name}) {
- $db->{perf} .= qq{ Table "$tname" on 2 has constraint "$name" on column "$cname", but 1 does not. };
- }
- else {
- $failcount--;
- }
- }
- }
+ ## Constraints are of differnet types (!)
+ for my $row (@{$fail{constraints}{difftype}}) {
+ my ($cname,$tname,$type1,$type2) = @$row;
+ $db->{perf} .= qq{ Constraint "$cname" on table "$tname" is type $type1 on 1, but $type2 on 2. };
}
- if (exists $fail{colconstraints}{tablediff}) {
- for my $row (@{$fail{colconstraints}{tablediff}}) {
- my ($name,$t1,$t2) = @$row;
- if (! exists $doublec{$name}) {
- $db->{perf} .= qq{ Constraint "$name" is applied to "$t1" on 1, but to "$t2" on 2. };
- }
- else {
- $failcount--;
- }
- }
- }
- if (exists $fail{colconstraints}{columndiff}) {
- for my $row (@{$fail{colconstraints}{columndiff}}) {
- my ($name,$t1,$c1,$t2,$c2) = @$row;
- if (! exists $doublec{$name}) {
- $db->{perf} .= qq{ Constraint "$name" on 1 is applied to $t1.$c1, but to $t2.$c2 on 2. };
- }
- else {
- $failcount--;
- }
- }
+
+ ## Constraints have a different key - rewrite prettier someday
+ for my $row (@{$fail{constraints}{diffkey}}) {
+ my ($cname,$tname,$key1,$key2) = @$row;
+ $db->{perf} .= qq{ Constraint "$cname" on table "$tname" is conkey $key1 on 1, but $key2 on 2. };
}
- 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 $t1) on 1 differs from 2 ("$d1" vs. "$d2") };
- }
- else {
- $failcount--;
- }
- }
+
+ ## Constraints have different source (as near as we can tell)
+ for my $row (@{$fail{constraints}{diffsrc}}) {
+ my ($cname,$tname,$cdef1,$cdef2) = @$row;
+ $db->{perf} .= qq{ Constraint "$cname" on table "$tname" differs in source: $cdef1 vs. $cdef2. };
}
}