Lots of testing work.
authorGreg Sabino Mullane <greg@endpoint.com>
Mon, 20 Jun 2011 16:54:22 +0000 (12:54 -0400)
committerGreg Sabino Mullane <greg@endpoint.com>
Mon, 20 Jun 2011 16:54:22 +0000 (12:54 -0400)
.gitignore
bucardo
t/02-bctl-goat.t [deleted file]
t/02-bctl-herd.t
t/02-bctl-table.t [new file with mode: 0644]
t/20-mongo.t
t/BucardoTesting.pm

index af1f7fac17c278e5ec4107719ba00ae529043936..496c09ece3b0afe2ce0c7f47d4fbf6449eb312c3 100644 (file)
@@ -21,6 +21,7 @@ pm_to_blib
 bucardo_test_database_*
 *.log
 log.bucardo
+test.*
 
 ## Temporary files:
 tmp/
diff --git a/bucardo b/bucardo
index 1038095be378452636ef057ada36c434007399f3..cc9e2f1e33ab3ff9d9a6825dd1909db48f9db25e 100755 (executable)
--- a/bucardo
+++ b/bucardo
@@ -1667,6 +1667,124 @@ sub list_dbgroups {
 } ## end of list_dbgroups
 
 
+##
+## Table-related subroutines: add, remove, update, list
+##
+
+sub add_table {
+
+    ## Add one or more tables. Inserts to the bucardo.goat table
+    ## May also update the bucardo.herd and bucardo.herdmap tables
+    ## Arguments: one or more
+    ## 1+ Names of tables to be added
+    ## Returns: undef
+    ## Example: bucardo add table pgbench_accounts foo% myschema.abc
+
+    ## Grab our generic usage message
+    my $usage = usage('add_table');
+
+    ## Must have at least one table
+    if (! @nouns) {
+        warn "$usage\n";
+        exit 1;
+    }
+
+    ## Inputs and aliases, database column name, flags, default
+    my $validcols = q{
+        db                       db                   0                null
+        ping                     ping                 TF               null
+        rebuild_index            rebuild_index        numeric          null
+        standard_conflict        standard_conflict    0                null
+        analyze_after_copy       analyze_after_copy   TF               null
+        herd                     herd                 0                skip
+    };
+
+    my ($dbcols,$cols,$phs,$vals,$extra)
+        = process_simple_args({cols => $validcols, list => \@nouns, usage => $usage});
+
+    ## Loop through all the args and attempt to add the tables
+    ## This returns a hash with the following keys: relations, match, nomatch
+    my $goatlist = get_goat_ids({args => \@nouns, dbcols => $dbcols});
+
+    ## The final output. Store it up all at once for a single QUIET check
+    my $msg = '';
+
+    ## We will be nice and indicate anything that did not match
+    if (keys %{ $goatlist->{nomatch} }) {
+        $msg .= "Did not find matches for the following terms:\n";
+        for (sort keys %{ $goatlist->{nomatch} }) {
+            $msg .= "  $_\n";
+        }
+    }
+
+    ## Now we need to output which ones were recently added
+    if (keys %{ $goatlist->{new} }) {
+        $msg .= "Added the following tables:\n";
+        for (sort keys %{ $goatlist->{new} }) {
+            $msg .= "  $_\n";
+        }
+    }
+
+    ## If they requested a herd and it does not exist, create it
+    if (exists $extra->{herd}) {
+        my $herdname = $extra->{herd};
+        if (! exists $HERD->{$herdname}) {
+            $SQL = 'INSERT INTO bucardo.herd(name) VALUES(?)';
+            $sth = $dbh->prepare($SQL);
+            $sth->execute($herdname);
+            $msg .= qq{Created the herd named "$herdname"\n};
+        }
+        ## Now load all of these tables into this herd
+        $SQL = 'INSERT INTO bucardo.herdmap (herd,priority,goat) VALUES (?,?,'
+            . q{ (SELECT id FROM goat WHERE schemaname||'.'||tablename=? AND db=?))};
+
+        $sth = $dbh->prepare($SQL);
+
+        ## Which tables were already in the herd, and which were just added
+        my (@oldnames,@newnames);
+
+        for my $name (sort keys %{ $goatlist->{relations} }) {
+            ## Is it already part of this herd?
+            if (exists $HERD->{goat}{$name}) {
+                push @oldnames => $name;
+                next;
+            }
+            my $db = $goatlist->{relations}{$name}{goat}{db};
+
+            my $pri = 0;
+
+            $count = $sth->execute($herdname,$pri,$name, $db);
+
+            push @newnames => $name;
+        }
+
+        if (@oldnames) {
+            $msg .= qq{The following tables were already in the herd "$herdname":\n};
+            for (@oldnames) {
+                $msg .= "  $_\n";
+            }
+        }
+
+        if (@newnames) {
+            $msg .= qq{The following tables are now part of the herd "$herdname":\n};
+            for (@newnames) {
+                $msg .= "  $_\n";
+            }
+        }
+
+    } ## end if herd
+
+    if (!$QUIET) {
+        print $msg;
+    }
+
+    confirm_commit();
+
+    return;
+
+} ## end of add_table
+
+
 ##
 ## Herd-related subroutines: add, remove, update, list
 ##
@@ -1684,21 +1802,21 @@ sub add_herd {
     ## Grab our generic usage message
     my $usage = usage('add_herd');
 
-    my $name = shift @nouns || '';
+    my $herdname = shift @nouns || '';
 
     ## Must have a name
-    if (!length $name) {
+    if (!length $herdname) {
         warn "$usage\n";
         exit 1;
     }
 
     ## Create the herd if it does not exist
-    if (exists $HERD->{$name}) {
-        print qq{Herd "$name" already exists\n};
+    if (exists $HERD->{$herdname}) {
+        print qq{Herd "$herdname" already exists\n};
     }
     else {
-        create_herd($name);
-        $QUIET or print qq{Created herd "$name"\n};
+        create_herd($herdname);
+        $QUIET or print qq{Created herd "$herdname"\n};
     }
 
     ## Everything else is tables or sequences to add to this herd
@@ -1712,79 +1830,68 @@ sub add_herd {
         return undef;
     }
 
-    ## First, see if we can find matches for known goats
-    ## We may be able to avoid a live db lookup if we find all the matches here
-    ## This returns a hash with the following keys: relations, match, nomatch
-    my $goatlist = get_goat_ids(@nouns);
+    ## Get the list of all requested tables, adding as needed
+    my $goatlist = get_goat_ids({args => \@nouns});
 
-    ## Figure out if we need to hit the live database to add potential tables
-    ## We generally do unless both of these conditions are met:
-    ## 1) Every search term is in the format "a.b" with no wildcards
-    my $nonstrictnames = grep { ! /^\w+\.\w+$/ } @nouns;
-    ## 2) We found a match in the goat table for every one of the above
-    my $goatnumber = keys %{ $goatlist->{relations} };
+    ## The final output. Store it up all at once for a single QUIET check
+    my $msg = '';
 
-    if ($nonstrictnames or $nouncount != $goatnumber) {
-        ## Which database do we perform the searches against?
-        my $bestdb = find_best_db_for_searching();
-        if (! defined $bestdb) {
-            die "No databases have been added yet, so we cannot add tables!\n";
+    ## We will be nice and indicate anything that did not match
+    if (keys %{ $goatlist->{nomatch} }) {
+        $msg .= "Did not find matches for the following terms:\n";
+        for (sort keys %{ $goatlist->{nomatch} }) {
+            $msg .= "  $_\n";
         }
+    }
 
-        ## Build a new list which excludes and that matched AND were in "x.y" form
-        my @newnouns;
-        for my $nam (@nouns) {
-            next if exists $goatlist->{match}{$nam} and $nam =~ /^\w+\.\w+$/o;
-            push @newnouns => $nam;
+    ## Now we need to output which ones were recently added
+    if (keys %{ $goatlist->{new} }) {
+        $msg .= "Added the following tables:\n";
+        for (sort keys %{ $goatlist->{new} }) {
+            $msg .= "  $_\n";
         }
-        my $livelist = get_live_goat_ids($bestdb, @newnouns);
+    }
 
-        ## Move these new ones to the existing goatlist hash
-        for my $nam (keys %{ $livelist->{relations} }) {
-            $goatlist->{relations}{$nam} ||= $livelist->{relations}{$nam};
-        }
 
-        ## Copy the 'nomatch' hash as well
-        for my $nam (keys %{ $livelist->{nomatch} }) {
-            ## Clobbering is okay here
-            warn "About to set nomatch for $nam\n";
-            warn Dumper $goatlist->{relations}{$nam};
-            warn "DONE with $nam\n";
-            $goatlist->{nomatch}{$nam} = $livelist->{nomatch}{$nam}
-                unless exists $goatlist->{match}{$name};
-        }
+    ## Now load all of these tables into this herd
+    $SQL = 'INSERT INTO bucardo.herdmap (herd,priority,goat) VALUES (?,?,'
+        . q{ (SELECT id FROM goat WHERE schemaname||'.'||tablename=? AND db=?))};
 
-    } ## end searching live database for matching relations
+    $sth = $dbh->prepare($SQL);
+
+    my (@oldnames, @newnames);
 
-    ## Output a message for all nouns that produced no matches at all
-    my %goat2add;
-    for my $nam (@nouns) {
-        if (exists $goatlist->{nomatch}{$nam}) {
-            ## XXX Fixme, giving output even when there is a match
-            #warn qq{No match found for: $nam\n};
+    for my $name (sort keys %{ $goatlist->{relations} }) {
+        ## Is it already part of this herd?
+        if (exists $HERD->{goat}{$name}) {
+            push @oldnames => $name;
+            next;
         }
-    }
+        my $db = $goatlist->{relations}{$name}{goat}{db};
 
-    ## Add each goat to the herdmap table
-    $SQL = q{INSERT INTO bucardo.herdmap(herd,goat) VALUES (?,?)};
-    my $addrow = $dbh->prepare($SQL);
-    for my $nam (sort keys %{ $goatlist->{relations} }) {
-        my $id = $GOAT->{$nam}{id}; #goatlist->{relations}{$nam}{goat}{id};
+        my $pri = 0;
 
-        if (exists $HERD->{$name}{goat}{$nam}) {
-            printf qq{%s "%s" already a part of this herd\n},
-                ucfirst ($GOAT->{$id}{reltype}), $name;
-            next;
+        $count = $sth->execute($herdname,$pri,$name, $db);
+
+        push @newnames => $name;
+    }
+
+    if (@oldnames) {
+        $msg .= qq{The following tables were already in the herd "$herdname":\n};
+        for (@oldnames) {
+            $msg .= "  $_\n";
         }
+    }
 
-        eval {
-            $addrow->execute($name,$id);
-        };
-        if ($@) {
-            die qq{Failed to add relation "$nam" to herd "$name"\n$@};
+    if (@newnames) {
+        $msg .= qq{The following tables are now part of the herd "$herdname":\n};
+        for (@newnames) {
+            $msg .= "  $_\n";
         }
-        printf qq{Added %s "%s" to the herd\n},
-            $GOAT->{$id}{reltype}, $nam;
+    }
+
+    if (!$QUIET) {
+        print $msg;
     }
 
     confirm_commit();
@@ -2245,13 +2352,6 @@ sub update {
         ## Do not report on the same one twice.
         ## Report all success, then all failures
 
-        ## Rethink: do this in two stages
-        ## First, find any matches for tables we already know about, in $global{goat}
-        ## Then, if needed, check the live database (add them as well)
-        ## At the end of the day we have a list of ids
-        ## Add those that are not already there
-        ## Complain if they don't fit (e.g. need PKs)
-        ## gregstop implement above
 
         ## First, find any matches for tables we already know about, in $global{goat}
         my $goatlist = get_goat_ids(@nouns);
@@ -3044,235 +3144,201 @@ sub add_goat_to_herd {
 sub get_goat_ids {
 
     ## Returns the ids from the goat table for matching relations
-    ## Arguments: variable  - names to match against
+    ## Also checks the live database and adds tables to the goat table as needed.
+    ## Arguments: single hashref:
+    ##  - args: arrayref of names to match against. Can have wildcards.
+    ##  - dbcols: optionl hashref of fields to populate goat table with (e.g. ping=1)
     ## Returns a hash with:
     ##  - relations: hash of goat objects, key is the fully qualified name
     ##    - original: hash of search term(s) used to find this
     ##    - goat: the goat object
     ##  - nomatch: hash of non-matching terms
     ##  - match: hash of matching terms
+    ##  - new: hash of newly added tables
 
-    my (%relation, %nomatch, %match, %seenit);
-
-    for my $item (@_) {
-
-        next if $seenit{$item}++;
-
-        my $hasadot = index($item,'.') >= 0 ? 1 : 0;
-        my $hasstar = (index($item,'*') >= 0 or index($item,'%') >= 0) ? 1 : 0;
-
-        ## Count the matches so we can return non-matching terms
-        my $found = 0;
-
-        ## Wildcards?
-        if ($hasstar) {
-
-            ## Change to a regexier form
-            my $original_item = $item;
-            $item =~ s/\./\\./g;
-            $item =~ s/[*%]/\.\*/g;
-
-            for my $fullname (grep { /\./ } keys %{ $GOAT }) {
-                ## If it has a dot, match the whole thing
-                if ($hasadot) {
-                    if ($fullname =~ /^$item$/) {
-                        $found++;
-                        $relation{$fullname}{original}{$original_item}++;
-                        $relation{$fullname}{goat} ||= $GOAT->{$fullname};
-                        $match{$item}++;
-                    }
-                    next;
-                }
-
-                ## No dot, so match table part only
-                my ($schema,$table) = split /\./ => $fullname;
-                if ($table =~ /^$item$/) {
-                    $found++;
-                    $relation{$fullname}{original}{$original_item}++;
-                    $relation{$fullname}{goat} ||= $GOAT->{$fullname};
-                    $match{$item}++;
-                }
-            }
-
-            if (! $found) {
-                $nomatch{$original_item}++;
-            }
-
-            next;
-
-        } ## end wildcards
-
-        ## If it has a dot, it must match exactly
-        if ($hasadot) {
-            if (exists $GOAT->{$item}) {
-                $relation{$item}{original}{$item}++;
-                $relation{$item}{goat} ||= $GOAT->{$item};
-                $match{$item}++;
-            }
-            else {
-                $nomatch{$item}++;
-            }
-
-            next;
-        }
-
-        ## No dot, so we match all tables regardless of the schema
-        for my $fullname (grep { /\./ } keys %{ $GOAT }) {
-            my ($schema,$table) = split /\./ => $fullname;
-            if ($table eq $item) {
-                $found++;
-                $relation{$fullname}{original}{$item}++;
-                $relation{$fullname}{goat} ||= $GOAT->{$fullname};
-                $match{$item}++;
-            }
-        }
-
-        if (! $found) {
-            $nomatch{$item}++;
-        }
-
-    } ## end each given needle
-
-    return { relations => \%relation, nomatch => \%nomatch, match => \%match };
+    my $arg = shift || {};
+    my $names = $arg->{args} or die;
+    my $dbcols = $arg->{dbcols} || {};
 
-} ## end of get_relation_ids
+    ## The final hash we return
+    my %relation;
 
+    ## Args that produced a match
+    my %match;
 
-sub get_live_goat_ids {
+    ## Args that produced no matches at all
+    my %nomatch;
 
-    ## Searches a source database for any matching goats not already known
-    ## If found, adds them to the goat table
-    ## Arguments: two or more
-    ## 1. Name of the source database to get items from
-    ## 2. One or more names to match against
-    ## Returns a hash with:
-    ##  - relations: hash of goat objects, key is the fully qualified name
-    ##    - original: hash of search term(s) used to find this
-    ##    - goat: the goat object
-    ##  - nomatch: hash of non-matching terms
-    ##  - match: hash of matching terms
+    ## Keep track of which args we've already done, just in case there are dupes
+    my %seenit;
 
-    my $dbname = shift;
+    ## Which tables we added to the goat table
+    my %new;
 
-    my (%relation, %nomatch, %seenit, %match);
+    ## Figure out which database to search in
+    my $bestdb = find_best_db_for_searching();
 
-    ## Tables that are not yet in the goat table: add in bulk
-    ## Keys are the "schema.table" name, values is hash with dbname and reltype
-    my %addtable;
+    ## This check still makes sense: if no databases, there should be nothing in $GOAT!
+    if (! defined $bestdb) {
+        die "No databases have been added yet, so we cannot add tables!\n";
+    }
 
-    my $rdbh = connect_database({name => $dbname}) or die;
+    my $rdbh = connect_database({name => $bestdb}) or die;
 
+    ## SQL to find a table or a sequence
+    ## We do not want pg_table_is_visible(c.oid) here
     my $BASESQL = q{
 SELECT nspname||'.'||relname AS name, relkind, c.oid
 FROM pg_class c
 JOIN pg_namespace n ON (n.oid = c.relnamespace)
-WHERE relkind IN ('S','r')
-AND pg_table_is_visible(c.oid)
+WHERE relkind IN ('r')
 AND nspname <> 'information_schema'
 AND nspname !~ '^pg_'
 };
 
-    for my $item (@_) {
+    ## Loop through each argument, and try and find matching goats
+  ITEM: for my $item (@$names) {
 
+        ## In case someone entered duplicate arguments
         next if $seenit{$item}++;
 
-        debug("Checking live db $dbname for $item");
+        ## Skip if this is not a tablename, but an arguement of the form x=y
+        next if index($item, '=') >= 0;
 
+        ## Determine if this item has a dot in it, and/or it is using wildcards
         my $hasadot = index($item,'.') >= 0 ? 1 : 0;
         my $hasstar = (index($item,'*') >= 0 or index($item,'%') >= 0) ? 1 : 0;
 
-        ## Count the matches so we can return non-matching terms
-        my $found = 0;
+        ## Temporary list of matching items
+        my @matches;
+
+        ## A list of tables to be bulk added to the goat table
+        my %addtable;
+
+        ## We may mutate the arg, so stow away the original
+        my $original_item = $item;
+
+        ## On the first pass, we look for matches in the existing $GOAT hash
+        ## We may also check the live database afterwards
 
         ## Wildcards?
         if ($hasstar) {
 
             ## Change to a regexier form
-            my $original_item = $item;
             $item =~ s/\./\\./g;
             $item =~ s/[*%]/\.\*/g;
+            $item = "^$item" if $item !~ /^[\^\.\%]/;
+            $item .= '$' if $item !~ /[\$\*]$/;
 
-            $SQL = $BASESQL . $hasadot ? "AND nspname||'.'||relname ~ ?" : "AND relname ~ ?";
+            ## Pull back all items from the GOAT hash that have a dot in them
+            for my $fullname (grep { /\./ } keys %{ $GOAT }) {
 
-            $sth = $rdbh->prepare($SQL);
-            $count = $sth->execute($item);
-            debug("Wildcard scan: $item. Count: $count");
-            for my $row (@{ $sth->fetchall_arrayref({}) }) {
-                my $name = $row->{name};
-                next if exists $GOAT->{$name};
-                $relation{$name}{original}{$original_item}++;
-                $match{$item}++;
-                $addtable{$name} = {db => $dbname, reltype => $row->{relkind}};
-                $found++;
-            }
+                ## We match against the whole thing if we have a dot
+                ## in our search term, otherwise we only match the table
+                my $searchname = $fullname;
+                if (! $hasadot) {
+                    (undef,$searchname) = split /\./ => $fullname;
+                }
 
-            if (! $found) {
-                $nomatch{$original_item}++;
+                ## Id we got a match, store the item from the GOAT that caused it
+                if ($searchname =~ /^$item$/) {
+                    push @matches => $fullname;
+                }
             }
 
-            next;
+            ## Setup the SQL to search the live database
+            $SQL = $BASESQL . ($hasadot
+                ? "AND nspname||'.'||relname ~ ?"
+                : "AND relname ~ ?");
 
         } ## end wildcards
 
-        ## If it has a dot, it must match exactly
-        if ($hasadot) {
-            next if exists $GOAT->{$item};
+        ## A dot with no wildcards: exact match
+        ## TODO: Allow foobar. to mean foobar.% ??
+        elsif ($hasadot) {
 
-            $SQL = $BASESQL . "AND nspname||'.'||relname = ?";
-            $sth = $rdbh->prepare($SQL);
-            $count = $sth->execute($item);
-            debug("Exact match $item. count is $count");
-            for my $row (@{ $sth->fetchall_arrayref({}) }) {
-                my $name = $row->{name};
-                next if exists $GOAT->{$name};
-                $relation{$name}{original}{$item}++;
-                $match{$item}++;
-                $addtable{$name} = {db => $dbname, reltype => $row->{relkind}};
-                $found++;
+            if (exists $GOAT->{$item}) {
+                push @matches => $item;
             }
 
-            if (! $found) {
-                $nomatch{$item}++;
-            }
+            ## No need to check live if we found a match
+            next ITEM if @matches;
 
-            next;
+            ## Setup the SQL to search the live database
+            $SQL = $BASESQL . q{AND nspname||'.'||relname = ?};
         }
 
-        ## No dot, so we match all tables regardless of the schema
+        ## No wildcards and no dot, so we match all tables regardless of the schema
+        else {
 
-        $SQL = $BASESQL . 'AND relname = ?';
+            ## Pull back all items from the GOAT hash that have a dot in them
+            for my $fullname (grep { /\./ } keys %{ $GOAT }) {
+                my ($schema,$table) = split /\./ => $fullname;
+                if ($table eq $item) {
+                    push @matches => $fullname;
+                }
+            }
+
+            ## Setup the SQL to search the live database
+            $SQL = $BASESQL . 'AND relname = ?';
+        }
+
+        ## Search the live database for matches
         $sth = $rdbh->prepare($SQL);
-        $count = $sth->execute($item);
-        debug("Table only $item, count is $count");
+        ($count = $sth->execute($item)) =~ s/0E0/0/;
+        debug(qq{Searched live database "$bestdb" for arg "$item", count was $count});
         for my $row (@{ $sth->fetchall_arrayref({}) }) {
+
+            ## The 'name' is combined "schema.relname"
             my $name = $row->{name};
+
+            ## Don't bother if we have already added this!
             next if exists $GOAT->{$name};
+
+            ## Document the string that led us to this one
             $relation{$name}{original}{$item}++;
+
+            ## Document the fact that we found this on a database
+            $new{$name}++;
+
+            ## Mark this item as having produced a match
             $match{$item}++;
-            $addtable{$name} = {db => $dbname, reltype => $row->{relkind}};
-            $found++;
-        }
 
-        if (! $found) {
-            $nomatch{$item}++;
+            ## Set this table to be added to the goat table below
+            $addtable{$name} = {db => $bestdb, reltype => $row->{relkind}, dbcols => $dbcols};
+
+            ## Add this to our matching list
+            push @matches => $name;
+
         }
 
-    } ## end each given needle
+        ## Add all the tables we just found from searching the live database
+        if (keys %addtable) {
+            add_items_to_goat_table(\%addtable);
+        }
 
-    ## Add all the tables we just found
-    my $newlist = add_items_to_goat_table(\%addtable);
+        ## Populate the final hashes based on the match list
+        for my $name (@matches) {
+            $relation{$name}{original}{$original_item}++;
+            $relation{$name}{goat} ||= $GOAT->{$name};
+            $match{$item}++;
+        }
 
-    ## Populate the newly added ones back into our hash
-    for my $name (keys %relation) {
-        if (! exists $GOAT->{$name}) {
-            die 'Something went wrong!';
+        ## If this item did not match anything, note that as well
+        if (! @matches) {
+            $nomatch{$original_item}++;
         }
-        $match{$name}{goat} ||= $GOAT->{$name};
-    }
 
-    return { relations => \%relation, nomatch => \%nomatch, match => \%match };
+    } ## end each given needle
+
+    return {
+        relations  => \%relation,
+        nomatch    => \%nomatch,
+        match      => \%match,
+        new        => \%new,
+    };
 
-} ## end of get_live_goat_ids
+} ## end of get_goat_ids
 
 
 sub add_items_to_goat_table {
@@ -3280,8 +3346,9 @@ sub add_items_to_goat_table {
     ## Given a list of tables, add them to the goat table as needed
     ## Arguments: 1
     ## 1. Hashref where keys are the relnames, and values are additional info:
-    ##   db: the database name (mandatory)
-    ##   reltype: table or sequence (optional, defaults to table)
+    ##   - db: the database name (mandatory)
+    ##   - reltype: table or sequence (optional, defaults to table)
+    ##   - dbcols: optional hashref of goat columns to set
     ## Returns: arrayref with all the new goat.ids
 
     my $info = shift or die;
@@ -3291,8 +3358,7 @@ sub add_items_to_goat_table {
     my $isthere = $dbh->prepare($SQL);
 
     ## SQL to add this new entry in
-    $SQL = "INSERT INTO bucardo.goat (schemaname,tablename,reltype,db) VALUES (?,?,?,?) RETURNING id";
-    my $newgoat = $dbh->prepare($SQL);
+    my $NEWGOATSQL = "INSERT INTO bucardo.goat (schemaname,tablename,reltype,db) VALUES (?,?,?,?) RETURNING id";
 
     my @newid;
 
@@ -3307,8 +3373,22 @@ sub add_items_to_goat_table {
         my $reltype = $info->{$name}{reltype} || 't';
         $reltype = $reltype =~ /s/i ? 'sequence' : 'table';
 
-        $newgoat->execute($schema,$table,$reltype,$db);
-        push @newid => $newgoat->fetchall_arrayref()->[0][0];
+        ## Adjust the SQL as necessary for this goat
+        $SQL = $NEWGOATSQL;
+        my @args = ($schema, $table, $reltype, $db);
+        if (exists $info->{$name}{dbcols}) {
+            for my $newcol (sort keys %{ $info->{$name}{dbcols} }) {
+                $SQL =~ s/\)/,$newcol)/;
+                $SQL =~ s/\?,/?,?,/;
+                push @args => $info->{$name}{dbcols}{$newcol};
+            }
+        }
+        $sth = $dbh->prepare($SQL);
+        ($count = $sth->execute(@args)) =~ s/0E0/0/;
+
+        debug(qq{Added "$schema.$table" to goat table with db "$db", count was $count});
+
+        push @newid => $sth->fetchall_arrayref()->[0][0];
     }
 
     ## Update the global
@@ -4294,17 +4374,17 @@ sub process_args {
     my %arg;
 
     while ($string =~ m/(\w+)\s*=\s*"(.+?)" /g) {
-        $arg{$1} = $2;
+        $arg{lc $1} = $2;
     }
     $string =~ s/\w+\s*=\s*".+?" / /g;
 
     while ($string =~ m/(\w+)\s*=\s*'(.+?)' /g) {
-        $arg{$1} = $2;
+        $arg{lc $1} = $2;
     }
     $string =~ s/\w+\s*=\s*'.+?' / /g;
 
     while ($string =~ m/(\w+)\s*=\s*(\S+)/g) {
-        $arg{$1} = $2;
+        $arg{lc $1} = $2;
     }
     $string =~ s/\w+\s*=\s*\S+/ /g;
 
@@ -4564,11 +4644,38 @@ sub list_tables {
         $maxdb = length $row->{db} if length $row->{db} > $maxdb;
    }
 
-    for my $row (@$info) {
-        printf "Table: %-*s  DB: %-*s  PK: %s\n",
+    for my $row (
+        map { $_->[0] }
+        sort {
+          $a->[1] cmp $b->[1]    ## Base schema name
+          or $a->[2] <=> $b->[2] ## Numeric schema name tail
+          or $a->[3] cmp $b->[3] ## Base table name
+          or $a->[4] <=> $b->[4] ## Numeric table name tail
+        }
+        map {
+          my $numerics = 0;
+          my $schemabase = $_->{schemaname};
+          $schemabase =~ s/(\d+)$// and $numerics = $1;
+          my $numerict = 0;
+          my $tablebase = $_->{tablename};
+          $tablebase =~ s/(\d+)$// and $numerict = $1;
+          [$_, $schemabase, $numerics, $tablebase, $numerict]
+        }
+    @$info) {
+        ## Extra information of note to tack on the end
+        my $extra = '';
+        if (defined $row->{ping}) {
+            $extra .= sprintf ' ping:%s', $row->{ping} ? 'true' : 'false';
+        }
+        if ($row->{rebuild_index} != 0) {
+            $extra .= " rebuild_index:$row->{rebuild_index}";
+        }
+
+        printf "Table: %-*s  DB: %-*s  PK: %s%s\n",
             $maxtable, "$row->{schemaname}.$row->{tablename}",
             $maxdb, $row->{db},
-            $row->{pk};
+            $row->{pk},
+            $extra;
         if ($VERBOSE) {
             my $g = $global{goat}->{$row->{id}};
             if (exists $g->{herd}) {
@@ -5299,180 +5406,6 @@ sub add_sync {
 } ## end of add_sync
 
 
-sub add_table {
-
-    ## Usage: add table [schema].table [options]
-
-    my $type = shift || 'table';
-
-    my $usage = usage("add_$type");
-
-    if (! @nouns) {
-        warn "$usage\n";
-        exit 1;
-    }
-
-    my $DEFAULT_SCHEMA = 'public';
-
-    ## Inputs and aliases, database column name, flags, default
-    my $validcols = q{
-        db                       db                   0                null
-        has_delta                has_delta            TF               null
-        ping                     ping                 TF               null
-        customselect             customselect         0                null
-        source_makedelta         source_makedelta     =inherits|on|off null
-        target_makedelta         target_makedelta     =inherits|on|off null
-        rebuild_index            rebuild_index        numeric          null
-        standard_conflict        standard_conflict    0                null
-        analyze_after_copy       analyze_after_copy   TF               null
-        delta_bypass             delta_bypass         TF               null
-        delta_bypass_min         delta_bypass_min     numeric          null
-        delta_bypass_count       delta_bypass_count   numeric          null
-        delta_bypass_percent     delta_bypass_percent numeric          null
-        delta_bypass             delta_bypass         TF               null
-        herd                     herd                 0                skip
-    };
-
-    my ($dbcols,$cols,$phs,$vals)
-        = process_simple_args({cols => $validcols, list => \@nouns, usage => $usage});
-
-    ## Any single words are table names.
-    my @tables;
-    for (@nouns) {
-        if (/(\w+)=(.+)/) {
-            $dbcols->{$1} = $2;
-        }
-        next if /=/;
-        if (/^(\w*?)?\.?(\w+)$/) {
-            push @tables => length $1 ? [$1,$2] : [$DEFAULT_SCHEMA,$2];
-        }
-        else {
-            warn "Invalid $type name: $_\n";
-            exit 1;
-        }
-    }
-
-    if (! @tables) {
-        warn "$usage\n";
-        exit 1;
-    }
-
-    if (! exists $dbcols->{db}) {
-        my $winner;
-        my $count = keys %$DB;
-        ## If we only have one database, we can use that
-        if ($count == 1) {
-            ($winner) = keys %{$global{db}};
-        }
-        else {
-            ## Since they did not provide one, pick the oldest postgres database
-            #die Dumper $DB->{mydb2};
-            for my $db (sort
-                        { $DB->{$a}{epoch} <=> $DB->{$b}{epoch} or $a cmp $b }
-                        grep { $DB->{$_}{dbtype} eq 'postgres' }
-                        keys %$DB) {
-                $winner = $db;
-                last;
-            }
-            if (! defined $winner) {
-                warn "Please specify a database with db=<name>\n";
-                _list_databases();
-                exit 1;
-            }
-        }
-
-        $dbcols->{db} = $winner;
-
-        ## Replace any existing database
-        if ($cols =~ s/\bdb\b//) {
-            $phs =~ s/\?//;
-            $phs =~ s/,,/,/;
-        }
-        ## Add this in to the start of the list
-        $cols = "db,$cols";
-        $vals->{db} = $dbcols->{db};
-        $phs .= ',?';
-        ## Cleanups
-        $cols =~ s/,,/,/;
-        $cols =~ s/,$//;
-        $phs =~ s/^,//;
-    }
-    my $db = $dbcols->{db};
-
-    if (! exists $global{db}{$db}) {
-        warn qq{Invalid database: "$db"\n};
-        _list_databases();
-        exit 1;
-    }
-
-    my $finalmsg = '';
-
-    ## If they requested a herd and it does not exist, create it
-    if (exists $dbcols->{herd}) {
-        my $herd = $dbcols->{herd};
-        if (! exists $global{herd}{$herd}) {
-            $SQL = 'INSERT INTO bucardo.herd(name) VALUES(?)';
-            $sth = $dbh->prepare($SQL);
-            $sth->execute($herd);
-            load_bucardo_info(1);
-            $finalmsg .= qq{Created herd "$herd"\n};
-        }
-    }
-
-    ## Attempt to insert these tables into the database if they don't already exist
-    $SQL = 'SELECT id FROM bucardo.goat WHERE schemaname=? AND tablename=? AND db=?';
-    my $sthf = $dbh->prepare($SQL);
-    $SQL = "INSERT INTO bucardo.goat (schemaname,tablename,reltype,$cols) VALUES (?,?,?,$phs)";
-    $DEBUG and warn "SQL: $SQL\n";
-    $DEBUG and warn Dumper $vals;
-    $sth = $dbh->prepare($SQL);
-    my $additions = '';
-    my %oldtable;
-    for my $row (@tables) {
-        my ($s,$t) = @$row;
-        $count = $sthf->execute($s,$t,$db);
-        if ($count >= 1) {
-            $oldtable{"$s.$t"}++;
-            next;
-        }
-        eval {
-            $sth->execute($s,$t,$type,$db);
-        };
-        if ($@) {
-            die qq{Failed to add $type "$s.$t": $@\n};
-        }
-        $additions .= qq{Added $type "$s.$t"\n};
-    }
-
-    ## Add them to the herd if it was specificed and they are new tables
-    if (exists $dbcols->{herd}) {
-        my $herd = $dbcols->{herd};
-        $SQL = 'INSERT INTO bucardo.herdmap (herd,priority,goat) VALUES (?,?,'
-            . ' (SELECT id FROM goat WHERE schemaname=? AND tablename=? AND db=?))';
-        $sth = $dbh->prepare($SQL);
-        for my $row (@tables) {
-            my ($s,$t) = @$row;
-            next if exists $oldtable{"$s.$t"};
-            eval {
-                $sth->execute($herd,0,$s,$t,$db);
-            };
-            if ($@) {
-                die qq{Failed to add $type "$s.$t" to herd "$herd": $@\n};
-            }
-        }
-    }
-
-    $dbh->commit();
-
-    if (!$QUIET) {
-        $finalmsg and print "$finalmsg";
-        print "$additions";
-    }
-
-    return;
-
-} ## end of add_table
-
 
 sub find_goats_in_db {
 
@@ -7754,6 +7687,7 @@ sub process_simple_args {
     }
 
     for my $arg (sort keys %$xyargs) {
+
         next if $arg eq 'extraargs';
 
         if (! exists $item{$arg}) {
diff --git a/t/02-bctl-goat.t b/t/02-bctl-goat.t
deleted file mode 100644 (file)
index bc21a6d..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env perl
-# -*-mode:cperl; indent-tabs-mode: nil-*-
-
-## Test adding, dropping, and changing databases via bucardo
-## Tests the main subs: add_database, list_databases, update_database, remove_database
-
-use 5.008003;
-use strict;
-use warnings;
-use Data::Dumper;
-use lib 't','.';
-use DBD::Pg;
-use Test::More tests => 47;
-
-use vars qw/$t $res $command $dbhX $dbhA $dbhB/;
-
-use BucardoTesting;
-my $bct = BucardoTesting->new({notime=>1})
-    or BAIL_OUT "Creation of BucardoTesting object failed\n";
-$location = '';
-
-## Make sure A and B are started up
-$dbhA = $bct->repopulate_cluster('A');
-$dbhB = $bct->repopulate_cluster('B');
-
-## Create a bucardo database, and install Bucardo into it
-$dbhX = $bct->setup_bucardo('A');
-
-## Grab connection information for each database
-my ($dbuserA,$dbportA,$dbhostA) = $bct->add_db_args('A');
-my ($dbuserB,$dbportB,$dbhostB) = $bct->add_db_args('B');
-
-exit;
-
-END {
-    $bct->stop_bucardo($dbhX);
-    $dbhX and $dbhX->disconnect();
-    $dbhA and $dbhA->disconnect();
-    $dbhB and $dbhB->disconnect();
-}
index 57843b84df098c697ea852e97041affbd81f8dfc..0997c279b43719536c904a2913988d3706cab929 100644 (file)
@@ -44,14 +44,14 @@ $t = q{Add herd gives expected message if herd already exists};
 $res = $bct->ctl('bucardo add herd foobar');
 like ($res, qr/Herd "foobar" already exists/, $t);
 
-$t = q{Add herd works when adding a single table that does not exist};
+$t = q{Add herd gives expected message when adding a single table that does not exist};
 $res = $bct->ctl('bucardo add herd foobar nosuchtable');
 like ($res, qr/No databases have been added yet/, $t);
 
 $t = q{Add herd works when adding a single table};
 $bct->ctl("bucardo add database bucardo_test user=$dbuserA port=$dbportA host=$dbhostA addalltables");
 $res = $bct->ctl('bucardo add herd foobar bucardo_test1');
-like ($res, qr/Herd "foobar" already exists .*Added table "public.bucardo_test1" to the herd plus/, $t);
+is ($res, qq{Herd "foobar" already exists\n$newherd_msg "foobar":\n  public.bucardo_test1\n}, $t);
 
 $t = q{Add herd works when adding multiple tables};
 
diff --git a/t/02-bctl-table.t b/t/02-bctl-table.t
new file mode 100644 (file)
index 0000000..ecdd0a3
--- /dev/null
@@ -0,0 +1,231 @@
+#!/usr/bin/env perl
+# -*-mode:cperl; indent-tabs-mode: nil-*-
+
+## Test adding, dropping, and changing tables via bucardo
+## Tests the main subs: add_table, list_table, update_table, remove_table
+
+use 5.008003;
+use strict;
+use warnings;
+use Data::Dumper;
+use lib 't','.';
+use DBD::Pg;
+use Test::More tests => 30;
+
+use vars qw/$t $res $expected $command $dbhX $dbhA $dbhB $SQL/;
+
+use BucardoTesting;
+my $bct = BucardoTesting->new({notime=>1})
+    or BAIL_OUT "Creation of BucardoTesting object failed\n";
+$location = '';
+
+## Make sure A and B are started up
+$dbhA = $bct->repopulate_cluster('A');
+$dbhB = $bct->repopulate_cluster('B');
+
+## Create a bucardo database, and install Bucardo into it
+$dbhX = $bct->setup_bucardo('A');
+
+## Grab connection information for each database
+my ($dbuserA,$dbportA,$dbhostA) = $bct->add_db_args('A');
+my ($dbuserB,$dbportB,$dbhostB) = $bct->add_db_args('B');
+
+## Tests of basic 'add table' usage
+
+$t = 'Add table with no argument gives expected help message';
+$res = $bct->ctl('bucardo add table');
+like ($res, qr/Usage: add table/, $t);
+
+$t = q{Add table fails when no databases have been created yet};
+$res = $bct->ctl('bucardo add table foobarz');
+like ($res, qr/No databases have been added yet/, $t);
+
+$bct->ctl("bucardo add db A dbname=bucardo_test user=$dbuserA port=$dbportA host=$dbhostA");
+
+$t = q{Add table fails when the table does not exist};
+$res = $bct->ctl('bucardo add table foobarz');
+like ($res, qr/Did not find matches.*  foobarz/s, $t);
+
+## Clear out each time, gather a list afterwards
+
+sub empty_goat_table() {
+    $SQL = 'TRUNCATE TABLE herdmap, herd, goat CASCADE';
+    $dbhX->do($SQL);
+    $dbhX->commit();
+}
+
+empty_goat_table();
+$t = q{Add table works for a single valid schema.table entry};
+$res = $bct->ctl('bucardo add table public.bucardo_test1');
+is ($res, qq{$addtable_msg:\n  public.bucardo_test1\n}, $t);
+
+$t = q{Add table fails for a single invalid schema.table entry};
+$res = $bct->ctl('bucardo add table public.bucardo_notest1');
+is ($res, qq{$nomatch_msg:\n  public.bucardo_notest1\n}, $t);
+
+$t = q{Add table works for a single valid table entry (no schema)};
+$res = $bct->ctl('bucardo add table bucardo_test2');
+is ($res, qq{$addtable_msg:\n  public.bucardo_test2\n}, $t);
+
+$t = q{Add table fails for a single invalid table entry (no schema)};
+$res = $bct->ctl('bucardo add table bucardo_notest2');
+is ($res, qq{$nomatch_msg:\n  bucardo_notest2\n}, $t);
+
+$dbhA->do('DROP SCHEMA IF EXISTS tschema CASCADE');
+$dbhA->do('CREATE SCHEMA tschema');
+$dbhA->do('CREATE TABLE tschema.bucardo_test3 (a int)');
+$dbhA->commit();
+
+$t = q{Add table works for multiple matching valid table entry (no schema)};
+$res = $bct->ctl('bucardo add table bucardo_test3');
+is ($res, qq{$addtable_msg:\n  public.bucardo_test3\n  tschema.bucardo_test3\n}, $t);
+
+$t = q{Add table works for a single valid middle wildcard entry};
+$res = $bct->ctl('bucardo add table b%_test4');
+is ($res, qq{$addtable_msg:\n  public.bucardo_test4\n}, $t);
+
+$t = q{Add table works for a single valid beginning wildcard entry};
+$res = $bct->ctl('bucardo add table %_test5');
+is ($res, qq{$addtable_msg:\n  public.bucardo_test5\n}, $t);
+
+$t = q{Add table works for a single valid ending wildcard entry};
+$res = $bct->ctl('bucardo add table drop%');
+is ($res, qq{$addtable_msg:\n  public.droptest\n}, $t);
+
+$t = q{Add table works for a single valid middle wildcard entry};
+$res = $bct->ctl('bucardo add table b%_test6');
+is ($res, qq{$addtable_msg:\n  public.bucardo_test6\n}, $t);
+
+$t = q{Add table fails for a single invalid wildcard entry};
+$res = $bct->ctl('bucardo add table b%_notest');
+is ($res, qq{$nomatch_msg:\n  b%_notest\n}, $t);
+
+$t = q{Add table works for a single valid schema wildcard entry};
+$res = $bct->ctl('bucardo add table %.bucardo_test7');
+is ($res, qq{$addtable_msg:\n  public.bucardo_test7\n}, $t);
+
+$t = q{Add table fails for a single invalid schema wildcard entry};
+$res = $bct->ctl('bucardo add table %.notest');
+is ($res, qq{$nomatch_msg:\n  %.notest\n}, $t);
+
+$t = q{Add table works for a single valid table wildcard entry};
+$res = $bct->ctl('bucardo add table public.bucard%8');
+is ($res, qq{$addtable_msg:\n  public.bucardo_test8\n}, $t);
+
+$t = q{Add table fails for a single invalid table wildcard entry};
+$res = $bct->ctl('bucardo add table public.no%test');
+is ($res, qq{$nomatch_msg:\n  public.no%test\n}, $t);
+
+$t = q{Add table works for a single valid schema and table wildcard entry};
+$res = $bct->ctl('bucardo add table pub%.bucard%9');
+is ($res, qq{$addtable_msg:\n  public.bucardo_test9\n}, $t);
+
+$t = q{Add table fails for a single invalid schema and table wildcard entry};
+$res = $bct->ctl('bucardo add table pub%.no%test');
+is ($res, qq{$nomatch_msg:\n  pub%.no%test\n}, $t);
+
+$t = q{Add table does not re-add existing tables};
+$res = $bct->ctl('bucardo add table bucard%');
+is ($res, qq{$addtable_msg:\n  public.bucardo_test10\n}, $t);
+
+$t = q{'bucardo list tables' returns expected result};
+$res = $bct->ctl('bucardo list tables');
+$expected =
+q{Table: public.bucardo_test1   DB: A  PK: id (int2)
+Table: public.bucardo_test2   DB: A  PK: id|data1 (int4|text)
+Table: public.bucardo_test3   DB: A  PK: id (int8)
+Table: public.bucardo_test4   DB: A  PK: id (text)
+Table: public.bucardo_test5   DB: A  PK: id space (date)
+Table: public.bucardo_test6   DB: A  PK: id (timestamp)
+Table: public.bucardo_test7   DB: A  PK: id (numeric)
+Table: public.bucardo_test8   DB: A  PK: id (bytea)
+Table: public.bucardo_test9   DB: A  PK: id (int_unsigned)
+Table: public.bucardo_test10  DB: A  PK: id (timestamptz)
+Table: public.droptest        DB: A  PK: none
+Table: tschema.bucardo_test3  DB: A  PK: none
+};
+is ($res, $expected, $t);
+
+## Remove them all, then try adding in various combinations
+empty_goat_table();
+$t = q{Add table works with multiple entries};
+$res = $bct->ctl('bucardo add table pub%.bucard%9 public.bucardo_test1 nada bucardo3 buca%2');
+is ($res, qq{$nomatch_msg:\n  bucardo3\n  nada\n$addtable_msg:\n  public.bucardo_test1\n  public.bucardo_test2\n  public.bucardo_test9\n}, $t);
+
+$t = q{Add table works when specifying the ping option};
+$res = $bct->ctl('bucardo add table bucardo_test4 ping=true');
+is ($res, qq{$addtable_msg:\n  public.bucardo_test4\n}, $t);
+
+$t = q{'bucardo list tables' returns expected result};
+$res = $bct->ctl('bucardo list tables');
+$expected =
+q{Table: public.bucardo_test1  DB: A  PK: id (int2)
+Table: public.bucardo_test2  DB: A  PK: id|data1 (int4|text)
+Table: public.bucardo_test4  DB: A  PK: id (text) ping:true
+Table: public.bucardo_test9  DB: A  PK: id (int_unsigned)
+};
+is ($res, $expected, $t);
+
+$t = q{Add table works when specifying the rebuild_index and ping options};
+$res = $bct->ctl('bucardo add table bucardo_test5 ping=false rebuild_index=1');
+is ($res, qq{$addtable_msg:\n  public.bucardo_test5\n}, $t);
+
+$t = q{'bucardo list tables' returns expected result};
+$res = $bct->ctl('bucardo list tables');
+$expected =
+q{Table: public.bucardo_test1  DB: A  PK: id (int2)
+Table: public.bucardo_test2  DB: A  PK: id|data1 (int4|text)
+Table: public.bucardo_test4  DB: A  PK: id (text) ping:true
+Table: public.bucardo_test5  DB: A  PK: id space (date) ping:false rebuild_index:1
+Table: public.bucardo_test9  DB: A  PK: id (int_unsigned)
+};
+is ($res, $expected, $t);
+
+empty_goat_table();
+
+$t = q{Add table works when adding to a new herd};
+$res = $bct->ctl('bucardo add table bucardo_test1 herd=foobar');
+$expected =
+qq{$addtable_msg:
+  public.bucardo_test1
+Created the herd named "foobar"
+$newherd_msg "foobar":
+  public.bucardo_test1
+};
+is ($res, $expected, $t);
+
+$t = q{Add table works when adding to an existing herd};
+$res = $bct->ctl('bucardo add table bucardo_test5 herd=foobar');
+is ($res, qq{$addtable_msg:\n  public.bucardo_test5\n$oldherd_msg "foobar":\n  public.bucardo_test5\n}, $t);
+
+$t = q{Add table works when adding multiple tables to a new herd};
+$res = $bct->ctl('bucardo add table "public.buc*3" %.bucardo_test2 herd=foobar2');
+$expected =
+qq{$addtable_msg:
+  public.bucardo_test2
+  public.bucardo_test3
+Created the herd named "foobar2"
+$newherd_msg "foobar2":
+  public.bucardo_test2
+  public.bucardo_test3
+};
+is ($res, $expected, $t);
+
+$t = q{Add table works when adding multiple tables to an existing herd};
+$res = $bct->ctl('bucardo add table bucardo_test6 %.%do_test4 herd=foobar2');
+$expected =
+qq{$addtable_msg:
+  public.bucardo_test4
+  public.bucardo_test6
+$newherd_msg "foobar2":
+  public.bucardo_test4
+  public.bucardo_test6
+};
+is ($res, $expected, $t);
+
+END {
+    $bct->stop_bucardo($dbhX);
+    $dbhX and $dbhX->disconnect();
+    $dbhA and $dbhA->disconnect();
+    $dbhB and $dbhB->disconnect();
+}
index 7766ecfafd08b411fe70a38f8569279b4a59d26e..5baac20005ba668bc60c65791b90230ff9bb3eef 100644 (file)
@@ -339,6 +339,19 @@ for my $table (keys %tabletype) {
     is ($count, 0, $t);
 }
 
+## Test of customselect options
+
+$t=q{Using customselect, we can force a text string to an int};
+my $CS = 'SELECT id, data1 AS data2inty::INTEGER, inty, email FROM bucardo.bucardo_test2';
+## Set this one for this db and this sync
+$bct->ctl('bucardo add cs db=M sync=mongo table=ttable');
+
+$t=q{Using customselect, we can restrict the columns sent};
+
+$t=q{Using customselect, we can add new columns and modify others};
+## Set this one for all syncs
+
+
 pass('Done with mongo testing');
 
 exit;
index b88b68eedb67e0f543e601f94402d9b3bc38f5f4..2f2b1147fc8b883d621b237373a4bf074ad2160c 100644 (file)
@@ -19,10 +19,17 @@ my $DEBUG = $ENV{BUCARDO_DEBUG} || 0;
 
 use base 'Exporter';
 our @EXPORT = qw/%tabletype %tabletypemysql %sequences %val compare_tables bc_deeply clear_notices 
-                 wait_for_notice $location is_deeply like pass is isa_ok ok/;
+                 wait_for_notice $location is_deeply like pass is isa_ok ok
+                 $oldherd_msg $newherd_msg $addtable_msg $nomatch_msg/;
 
 my $dbname = 'bucardo_test';
 
+## Shortcuts for ease of changes and smaller text:
+our $addtable_msg = 'Added the following tables';
+our $nomatch_msg = 'Did not find matches for the following terms';
+our $oldherd_msg = 'The following tables are now part of the herd';
+our $newherd_msg = 'The following tables are now part of the herd';
+
 our $location = 'setup';
 my $testmsg  = ' ?';
 my $testline = '?';
@@ -996,6 +1003,10 @@ sub setup_bucardo {
 sub thing_exists {
     my ($dbh,$name,$table,$column) = @_;
     my $SQL = "SELECT 1 FROM $table WHERE $column = ?";
+    ## Only want tables from the public schema for now
+    if ($table eq 'pg_class') {
+        $SQL .= qq{ AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')};
+    }
     my $sth = $dbh->prepare($SQL);
     $count = $sth->execute($name);
     $sth->finish();
@@ -1361,7 +1372,6 @@ sub add_bucardo_schema_to_database {
 
 
 
-
 sub add_test_tables_to_herd {
 
     ## Add all of the test tables (and sequences) to a herd
@@ -1492,8 +1502,8 @@ sub like($$;$) {
     if ($bail_on_error and ++$total_errors => $bail_on_error) {
         my $line = (caller)[2];
         my $time = time;
-        Test::More::diag("GOT: ".Dumper $_[0]);
-        Test::More::diag("EXPECTED: ".Dumper $_[1]);
+#        Test::More::diag("GOT: ".Dumper $_[0]);
+#        Test::More::diag("EXPECTED: ".Dumper $_[1]);
         Test::More::BAIL_OUT "Stopping on a failed 'like' test from line $line. Time: $time";
     }
 } ## end of like
@@ -1505,6 +1515,27 @@ sub is($$;$) {
     t($_[2],(caller)[2]);
     my $rv = Test::More::is($_[0],$_[1],$testmsg);
     return $rv if $rv;
+    ## Where exactly did this fail?
+    my $char = 0;
+    my $onelen = length $_[0];
+    my $twolen = length $_[1];
+    my $line = 1;
+    my $lchar = 1;
+    for ($char = 0; $char < $onelen and $char < $twolen; $char++) {
+        my $one = ord(substr($_[0],$char,1));
+        my $two = ord(substr($_[1],$char,1));
+        if ($one != $two) {
+            Test::More::diag("First difference at character $char ($one vs $two) (line $line, char $lchar)");
+            last;
+        }
+        if (10 == $one) {
+            $line++;
+            $lchar = 1;
+        }
+        else {
+            $lchar++;
+        }
+    }
     if ($bail_on_error and ++$total_errors => $bail_on_error) {
         my $line = (caller)[2];
         my $time = time;