usage_exit($doc_section) unless length $item_name && length $newname;
## Does this number or name exist?
- if (! exists $GOAT->{$item_name}) {
+ my $goat;
+ if (exists $GOAT->{by_fullname}{$item_name}) {
+ $goat = $GOAT->{by_fullname}{$item_name};
+ }
+ elsif (exists $GOAT->{by_table}{$item_name}) {
+ $goat = $GOAT->{by_table}{$item_name};
+ }
+ elsif (exists $GOAT->{by_id}{$item_name}) {
+ $goat = $GOAT->{by_id}{$item_name};
+ }
+ else {
print qq{Could not find a matching table for "$item_name"\n};
exit 1;
}
- ## Gather the name variations
- my $goat = $GOAT->{$item_name};
## If this is a ref due to it being an unqualified name, just use the first one
$goat = $goat->[0] if ref $goat eq 'ARRAY';
my ($sname,$tname) = ($goat->{schemaname},$goat->{tablename});
push @oldnames => $name;
next;
}
- my $db = $goatlist->{relations}{$name}{goat}{db};
+ # XXX This [0] might cause problems... it's new behavior
+ my $db = $goatlist->{relations}{$name}{goat}[0]{db};
my $pri = 0;
for my $name (@nouns) {
if ($name =~ /^\w[\w\d]*\.\w[\w\d]*$/) {
- if (! exists $GOAT->{$name}) {
+ if (! exists $GOAT->{by_fullname}{$name}) {
print qq{No such $reltype: $name\n};
next;
}
exit 0 if ! check_recurse($GOAT, $name, @actions);
## Make sure this table exists!
+ ## XXX JWT probably check database as well, not just name
my $table = $GOAT->{$name} or die qq{Could not find a table named "$name"\n}
. "Use 'list tables' to see all available.\n";
if ($dbcols->{herd} =~ /,/) {
my @tables = split /\s*,\s*/ => $dbcols->{herd};
for my $table (sort @tables) {
- if (! exists $global{goat}{$table}) {
+ if (! exists $global{goat}{by_fullname}{$table}) {
die "No such table or sequence: $table\n";
}
}
my $schema = 'public';
## Maybe it's a table?
- if (! exists $global{goat}{$name}) {
+ die Dumper($name, $global{goat});
+ if (! exists $global{goat}{by_fullname}{$name}) {
warn "No such relgroup or table: $name\n";
warn "To list all relgroups, use: list relgroups\n";
warn "To list all tables, use: list tables\n";
$item = "^$item" if $item !~ /^[\^\.\%]/;
$item .= '$' if $item !~ /[\$\*]$/;
## Pull back all items from the GOAT hash that have a dot in them
- for my $fullname (grep { /\./ } keys %{ $GOAT }) {
+ for my $fullname (grep { /\./ } keys %{ $GOAT->{by_fullname} }) {
## We match against the whole thing if we have a dot
## in our search term, otherwise we only match the table
## TODO: Allow foobar. to mean foobar.% ??
elsif ($hasadot) {
- if (exists $GOAT->{$item}
- and $GOAT->{$item}{reltype} eq $reltype) {
+ if ((exists $GOAT->{by_fullname}{$item}
+ and $GOAT->{by_fullname}{$item}{reltype} eq $reltype) ||
+ (exists $GOAT->{by_table}{$item}
+ and $GOAT->{by_table}{$item}{reltype} eq $reltype)) {
push @matches => $item;
}
else {
## Pull back all items from the GOAT hash that have a dot in them
- for my $fullname (grep { /\./ } keys %{ $GOAT }) {
+ for my $fullname (grep { /\./ } keys %{ $GOAT->{by_fullname} }) {
my ($schema,$table) = split /\./ => $fullname;
if ($table eq $item) {
push @matches => $fullname;
my $name = $row->{name};
## Don't bother if we have already added this!
- next if exists $GOAT->{$name};
+ next if exists $GOAT->{by_fullname}{$name};
## Document the string that led us to this one
$relation{$name}{original}{$item}++;
## Populate the final hashes based on the match list
for my $name (@matches) {
$relation{$name}{original}{$original_item}++;
- $relation{$name}{goat} ||= $GOAT->{$name};
+ $relation{$name}{goat} ||= $GOAT->{by_fullname}{$name};
$match{$item}++;
}
## Return a list of goat objects
my %newlist;
for my $id (@newid) {
- my $goat = $global{goat}{$id};
+ my $goat = $global{goat}{by_id}{$id};
my $name = "$goat->{schemaname}.$goat->{tablename}";
$newlist{$name} = $goat;
}
## Make sure that each herd with this table also has this new table
my $ggoat = $global{goat};
my $hherd = $global{herd};
- for my $herd (sort keys %{$ggoat->{$id}{herd}}) {
+ for my $herd (sort keys %{$ggoat->{by_id}{$id}{herd}}) {
$seenit{fktable} = 1;
next if exists $hherd->{$herd}{hasgoat}{$schema}{$table};
printf "Table %s.%s is in relgroup %s, but %s.%s (used as FK%s) is not\n",
}
## Is this a valid gaot?
+ die "There's a bug here";
if ($extras->{relation} and ! exists $global{goat}{$extras->{relation}}) {
die qq{Unknown relation: $extras->{relation}\n};
}
$SQL = 'SELECT * FROM bucardo.goat';
$sth = $dbh->prepare($SQL);
$sth->execute();
- my $goat = $sth->fetchall_hashref('id');
- ## Since relations cannot start with a number, we can also safely add the name to the hash
- for my $key (%$goat) {
+
+ my $goat;
+ $goat->{by_id} = $sth->fetchall_hashref('id');
+ $goat->{by_table} = {};
+
+ for my $key (%{$goat->{by_id}}) {
next if $key !~ /^\d/;
- my $tname = $goat->{$key}{tablename};
- my $name = "$goat->{$key}{schemaname}.$tname";
- $goat->{$name} = $goat->{$key};
+ my $tname = $goat->{by_id}{$key}{tablename};
+ my $name = "$goat->{by_id}{$key}{schemaname}.$tname";
+ my $dbname = $goat->{by_id}{$key}{db};
+
+ ## Index by database, so different databases containing matching object
+ ## names can be handled
+ $goat->{by_db}{$dbname}{$name} = $goat->{by_id}{$key};
+
+ ## Index by full object name
+ if (! exists $goat->{by_fullname}{$name}) {
+ $goat->{by_fullname}{$name} = [ $goat->{by_id}{$key} ];
+ }
+ else {
+ push @{$goat->{by_fullname}{$name}}, $goat->{by_id}{$key};
+ }
+
## Also want a table-only version:
- push @{$goat->{$tname}} => $goat->{$key};
+ $goat->{by_table}{$tname} = [] unless exists $goat->{by_table}{$tname};
+ push @{$goat->{by_table}{$tname}} => $goat->{by_id}{$key};
}
## Grab all herd information
for my $row (@{$sth->fetchall_arrayref({})}) {
my ($g,$h,$p) = @$row{qw/goat herd priority/};
$goat->{$g}{herd}{$h} = $p;
- $herd->{$h}{goat}{"$goat->{$g}{schemaname}.$goat->{$g}{tablename}"} = {
+ $herd->{$h}{goat}{"$goat->{by_id}{$g}{schemaname}.$goat->{by_id}{$g}{tablename}"} = {
id => $g,
priority => $p,
- reltype => $goat->{$g}{reltype},
- schema => $goat->{$g}{schemaname},
- table => $goat->{$g}{tablename},
+ reltype => $goat->{by_id}{$g}{reltype},
+ schema => $goat->{by_id}{$g}{schemaname},
+ table => $goat->{by_id}{$g}{tablename},
};
- my ($s,$t) = @{$goat->{$g}}{qw/schemaname tablename/};
+ my ($s,$t) = @{$goat->{by_id}{$g}}{qw/schemaname tablename/};
$herd->{$h}{hasgoat}{$s}{$t} = $p;
## Assign each herd to a datbase via its included goats
- $herd->{$h}{db} = $goat->{$g}{db};
+ $herd->{$h}{db} = $goat->{by_id}{$g}{db};
}
## Grab all sync information
}
## Note which syncs are used by each goat
for my $row2 (sort keys %{$row->{herd}{goat}}) {
- $goat->{$row2}{sync}{$name} = 1;
+ $goat->{by_id}{$row2}{sync}{$name} = 1;
}
}
$global{sync} = $SYNC = $sync;
## Separate goat into tables and sequences
- for my $id (keys %$GOAT) {
+ for my $id (keys %{$GOAT->{by_id}}) {
## Ids only please
next if $id !~ /^\d+$/;
- my $type = $GOAT->{$id}{reltype};
+ my $type = $GOAT->{by_id}{$id}{reltype};
if ($type eq 'table') {
- $TABLE->{$id} = $GOAT->{$id};
+ $TABLE->{$id} = $GOAT->{by_id}{$id};
}
elsif ($type eq 'sequence') {
- $SEQUENCE->{$id} = $GOAT->{$id};
+ $SEQUENCE->{$id} = $GOAT->{by_id}{$id};
}
else {
die "Unknown relation type $type!";
}
}
+ warn Dumper($goat);
return;
} ## end of load_bucardo_info
--- /dev/null
+#!/usr/bin/env perl
+# -*-mode:cperl; indent-tabs-mode: nil-*-
+
+## Test handling of object names
+
+use 5.008003;
+use strict;
+use warnings;
+use lib 't','.';
+use DBD::Pg;
+use Test::More tests => 50;
+
+use BucardoTesting;
+my $bct = BucardoTesting->new({ location => 'makedelta' })
+ or BAIL_OUT "Creation of BucardoTesting object failed\n";
+
+END { $bct->stop_bucardo if $bct }
+
+ok my $dbhA = $bct->repopulate_cluster('A'), 'Populate cluster A';
+ok my $dbhB = $bct->repopulate_cluster('B'), 'Populate cluster B';
+ok my $dbhC = $bct->repopulate_cluster('C'), 'Populate cluster C';
+ok my $dbhD = $bct->repopulate_cluster('D'), 'Populate cluster D';
+ok my $dbhX = $bct->setup_bucardo('A'), 'Set up Bucardo';
+
+END { $_->disconnect for grep { $_ } $dbhA, $dbhB, $dbhC, $dbhD, $dbhX }
+
+# Teach Bucardo about the databases.
+for my $db (qw(A B C D)) {
+ my ($user, $port, $host) = $bct->add_db_args($db);
+ like $bct->ctl(
+ "bucardo add db $db dbname=bucardo_test user=$user port=$port host=$host"
+ ), qr/Added database "$db"/, qq{Add database "$db" to Bucardo};
+}
+
+for my $arr ((['A','B'], ['C','D'])) {
+ my ($src, $dest) = @$arr;
+ print STDERR "\$src: $src \$dest: $dest\n";
+ like $bct->ctl("bucardo add table bucardo_test1 db=$src relgroup=myrels_$src"),
+ qr/Added the following tables/, "Added table in db $src ";
+ like $bct->ctl("bucardo add sync test_$src relgroup=myrels_$src dbs=$src:source,$dest:target"),
+ qr/Added sync "test_$src"/, "Create sync from $src to $dest";
+}
+die;
+#
+# # Create a sync for multi-master replication between A and B
+# like $bct->ctl('bucardo add sync deltatest1 relgroup=myrels dbs=A:source,B:source'),
+# qr/Added sync "deltatest1"/, 'Create sync "deltatest1"';
+#
+# # Create a sync for replication from B to C
+# like $bct->ctl('bucardo add sync deltatest2 relgroup=myrels dbs=B,C'),
+# qr/Added sync "deltatest2"/, 'Create sync "deltatest2"';
+#
+# # Listen in on things.
+# ok $dbhX->do('LISTEN bucardo_syncdone_deltatest1'),
+# 'Listen for syncdone_deltatest1';
+# ok $dbhX->do('LISTEN bucardo_syncdone_deltatest2'),
+# 'Listen for syncdone_deltatest2';
+# ok $dbhX->do('LISTEN bucardo_syncdone_deltatest3'),
+# 'Listen for syncdone_deltatest3';
+#
+# # Start up Bucardo and wait for initial syncs to finish.
+# ok $bct->restart_bucardo($dbhX), 'Bucardo should start';
+# ok $bct->wait_for_notice($dbhX, [qw(
+# bucardo_syncdone_deltatest1
+# bucardo_syncdone_deltatest2
+# )]), 'The deltatest1 and deltatest2 syncs should finish';
+#
+# # Should have no rows.
+# $bct->check_for_row([], [qw(A B C)], undef, 'test[124]$');
+#
+# # Let's add some data into A.bucardo_test1.
+# ok $dbhA->do(q{INSERT INTO bucardo_test1 (id, data1) VALUES (1, 'foo')}),
+# 'Insert a row into test1 on A';
+# $bct->ctl('bucardo message Adding new row to bucardo_test1');
+# $dbhA->commit;
+#
+# ok $bct->wait_for_notice($dbhX, [qw(
+# bucardo_syncdone_deltatest1
+# )]), 'The deltatest1 sync finished';
+#
+# # The row should be in A and B, but not C (as we have not kicked deltatest2 yet)
+# is_deeply $dbhB->selectall_arrayref(
+# 'SELECT id, data1 FROM bucardo_test1'
+# ), [[1, 'foo']], 'Should have the test1 row in B';
+#
+# is_deeply $dbhC->selectall_arrayref(
+# 'SELECT id, data1 FROM bucardo_test1'
+# ), [], 'No rows in C yet';
+#
+# # Kick the second sync so that we get the row into C
+# $bct->ctl('bucardo kick sync deltatest2 0');
+#
+# # Now the row should be in C
+# is_deeply $dbhC->selectall_arrayref(
+# 'SELECT id, data1 FROM bucardo_test1'
+# ), [[1, 'foo']], 'Should have the test1 row in C';
+#
+# # Excellent. Now let's insert into test2 on B.
+# ok $dbhB->do(q{INSERT INTO bucardo_test2 (id, data1) VALUES (2, 'foo')}),
+# 'Insert a row into test2 on B';
+# $dbhB->commit;
+#
+# ok $bct->wait_for_notice($dbhX, [qw(
+# bucardo_syncdone_deltatest1
+# bucardo_syncdone_deltatest2
+# )]), 'The deltatest1 and deltatest2 syncs finished';
+#
+# is_deeply $dbhA->selectall_arrayref(
+# 'SELECT id, data1 FROM bucardo_test2'
+# ), [[2, 'foo']], 'Should have the A test2 row in A';
+#
+# is_deeply $dbhC->selectall_arrayref(
+# 'SELECT id, data1 FROM bucardo_test2'
+# ), [[2, 'foo']], 'Should have the A test2 row in C';
+#
+# # Finally, try table 4, which has no makedelta.
+# ok $dbhA->do(q{INSERT INTO bucardo_test4 (id, data1) VALUES (3, 'foo')}),
+# 'Insert a row into test4 on A';
+# $dbhA->commit;
+#
+# ok $bct->wait_for_notice($dbhX, [qw(
+# bucardo_syncdone_deltatest1
+# )]), 'The deltatest1 sync finished';
+#
+# # Kick off the second sync
+# $bct->ctl('bucardo kick sync deltatest2 0');
+#
+# is_deeply $dbhB->selectall_arrayref(
+# 'SELECT id, data1 FROM bucardo_test4'
+# ), [[3, 'foo']], 'Should have the test4 row in B';
+#
+# is_deeply $dbhC->selectall_arrayref(
+# 'SELECT id, data1 FROM bucardo_test4'
+# ), [], 'Should have no test4 row row in C';
+#
+# $dbhA->commit();
+# $dbhB->commit();
+# $dbhC->commit();
+#
+# ##############################################################################
+# # Okay, what if we have C be a target from either A or B?
+# like $bct->ctl('bucardo remove sync deltatest2'),
+# qr/Removed sync "deltatest2"/, 'Remove sync "deltatest2"';
+# like $bct->ctl('bucardo add sync deltatest3 relgroup=myrels dbs=A:source,B:source,C'),
+# qr/Added sync "deltatest3"/, 'Created sync "deltatest3"';
+#
+# ok $bct->restart_bucardo($dbhX), 'Bucardo restarted';
+#
+# ok $dbhA->do(q{INSERT INTO bucardo_test2 (id, data1) VALUES (3, 'howdy')}),
+# 'Insert a row into test2 on A';
+# $dbhA->commit;
+#
+# ok $bct->wait_for_notice($dbhX, [qw(
+# bucardo_syncdone_deltatest1
+# bucardo_syncdone_deltatest3
+# )]), 'Syncs deltatest1 and deltatest3 finished';
+#
+# is_deeply $dbhB->selectall_arrayref(
+# 'SELECT id, data1 FROM bucardo_test2'
+# ), [[2, 'foo'], [3, 'howdy']], 'Should have the A test2 row in B';
+#
+# is_deeply $dbhC->selectall_arrayref(
+# 'SELECT id, data1 FROM bucardo_test2'
+# ), [[2, 'foo'], [3, 'howdy']], 'Should have the A test2 row in C';
+#