Database-Join
view release on metacpan or search on metacpan
_data => $opts{_data} // {},
_select_calls => 0,
_auth_calls => 0,
_quit_calls => 0,
}, $class;
}
sub auth { my $self = shift; $self->{_auth_calls}++; return 'OK' }
sub quit { my $self = shift; $self->{_quit_calls}++; return 'OK' }
sub select { my ($self, $idx) = @_; $self->{_select_calls}++; return 'OK' }
sub keys {
my ($self, $pattern) = @_;
my $table = $self->{_table};
(my $prefix = $pattern) =~ s/\*\z//;
return grep { /\A\Q$prefix\E/ }
map { "${table}:$_" } sort CORE::keys %{ $self->{_data} };
}
sub hgetall {
my ($self, $key) = @_;
my $table = $self->{_table};
(my $entry = $key) =~ s/\A\Q${table}\E://;
my $row = $self->{_data}{$entry} or return ();
return %{$row};
}
}
# Determine which Redis class DA's _open() will actually use
my $REDIS_CLASS = defined $INC{'Redis/Fast.pm'} ? 'Redis::Fast' : 'Redis';
# Helper: return a fresh MockRedis and a mock_scoped guard
sub _mock_redis {
my (%opts) = @_;
my $mock = MockRedis->new(
_table => $opts{table} // 'dj_rA',
_data => $opts{data} // \%REDIS_FIXTURE,
);
my $guard = mock_scoped($REDIS_CLASS, new => sub { $mock });
return ($mock, $guard);
}
# ---------------------------------------------------------------------------
# InlineDA â minimal blessed stub for the non-Redis side of the join.
# Must implement columns(), selectall_arrayref(), schema(), updated(),
# set_logger() to satisfy Database::Join's component contract.
# ---------------------------------------------------------------------------
{
package InlineDA;
sub new {
my ($class, %args) = @_;
return bless {
_cols => $args{cols} // [],
_rows => $args{rows} // [],
}, $class;
}
sub columns { return $_[0]->{_cols} }
sub schema { return {} }
sub updated { return 1 }
sub set_logger { return $_[0] }
sub selectall_arrayref {
my ($self, $criteria) = @_;
my @rows = @{ $self->{_rows} };
for my $col (CORE::keys %{ $criteria // {} }) {
my $val = $criteria->{$col};
next if ref $val;
@rows = grep { defined $_->{$col} && $_->{$col} eq $val } @rows;
}
return \@rows;
}
}
# Build a fresh InlineDA with the standard fixture columns
sub _inline_da {
return InlineDA->new(
cols => [qw(entry tier region)],
rows => [@INLINE_ROWS],
);
}
# ---------------------------------------------------------------------------
# Section 1 â constructor
# ---------------------------------------------------------------------------
subtest 'R1: Database::Join accepts a Redis-backed DA' => sub {
plan tests => 1;
my ($mock, $guard) = _mock_redis();
my $redis_da = Database::dj_rA->new(database => $REDIS_URL);
lives_ok {
Database::Join->new(
databases => [$redis_da, _inline_da()],
join_column => 'entry',
);
} 'new() with Redis DA lives';
};
# ---------------------------------------------------------------------------
# Section 2 â columns() and schema()
# ---------------------------------------------------------------------------
subtest 'R2: columns() returns merged columns from both DAs' => sub {
plan tests => 3;
my ($mock, $guard) = _mock_redis();
my $redis_da = Database::dj_rA->new(database => $REDIS_URL);
$redis_da->count(); # trigger Redis _open() while guard is active
my $join = Database::Join->new(
databases => [$redis_da, _inline_da()],
join_column => 'entry',
);
my $cols = $join->columns();
returns_is($cols, { type => 'arrayref' }, 'columns() returns arrayref');
my %col_set = map { $_ => 1 } @{$cols};
ok($col_set{name}, 'columns() includes Redis column name');
ok($col_set{tier}, 'columns() includes InlineDA column tier');
};
# ---------------------------------------------------------------------------
# Section 3 â count()
# ---------------------------------------------------------------------------
subtest 'R3: count() == 4 for left join (primary is Redis, 4 Redis rows)' => sub {
( run in 0.393 second using v1.01-cache-2.11-cpan-7da0f45c409 )