Database-Join
view release on metacpan or search on metacpan
t/sqlite-backend.t view on Meta::CPAN
}
sub selectall_arrayref {
my ($self, @args) = @_;
$self->{_call_count}++;
return $self->SUPER::selectall_arrayref(@args);
}
sub call_count { return $_[0]->{_call_count} }
1;
}
subtest 'cache reuse: source DA selectall_arrayref called once, not per query' => sub {
my $da_a = CountingDA->new(
cols => [qw(id name)],
rows => [
{ id => 'k1', name => 'Alice' },
{ id => 'k2', name => 'Bob' },
],
);
my $da_b = CountingDA->new(
cols => [qw(id score)],
rows => [
{ id => 'k1', score => 95 },
{ id => 'k2', score => 72 },
],
);
my $join = Database::Join->new(
databases => [$da_a, $da_b],
join_column => $JOIN_COL,
backend => 'sqlite',
);
my $r1 = $join->selectall_arrayref(); # first call: builds cache
my $r2 = $join->selectall_arrayref(); # second: reuses cache
my $r3 = $join->selectall_arrayref(); # third: reuses cache
is $da_a->call_count, 1,
'primary DA: selectall_arrayref called once (cache build only)';
is $da_b->call_count, 1,
'secondary DA: selectall_arrayref called once (cache build only)';
is scalar @{$r1}, 2, 'first query returns 2 rows';
is scalar @{$r2}, 2, 'second query returns 2 rows (cache reused)';
is scalar @{$r3}, 2, 'third query returns 2 rows (cache reused)';
};
# ===========================================================================
# S17: Cache invalidation â rebuilt when any source updated() changes
# The cache tracks updated() timestamps; a changed timestamp forces a full
# rebuild so queries see the new source data.
# ===========================================================================
{
package UpdatableDA;
use parent -norequire, 'CountingDA';
sub new {
my ($class, %args) = @_;
my $self = $class->SUPER::new(%args);
$self->{_ts} = $args{ts} // 1000;
return $self;
}
sub updated { return $_[0]->{_ts} }
sub set_updated { $_[0]->{_ts} = $_[1] }
sub set_rows { $_[0]->{_rows} = $_[1] }
1;
}
subtest 'cache invalidation: cache rebuilt when source updated() timestamp changes' => sub {
my $da_a = UpdatableDA->new(
cols => [qw(id name)],
rows => [{ id => 'k1', name => 'Alice' }],
ts => 1000,
);
my $da_b = UpdatableDA->new(
cols => [qw(id score)],
rows => [{ id => 'k1', score => 10 }],
ts => 2000,
);
my $join = Database::Join->new(
databases => [$da_a, $da_b],
join_column => $JOIN_COL,
backend => 'sqlite',
);
my $r1 = $join->selectall_arrayref();
is $r1->[0]{score}, 10, 'initial query: score=10';
is $da_b->call_count, 1, 'secondary DA called once for initial build';
# Simulate source update: change data and bump the timestamp.
$da_b->set_rows([{ id => 'k1', score => 99 }]);
$da_b->set_updated(2001);
my $r2 = $join->selectall_arrayref();
is $r2->[0]{score}, 99, 'after update: cache rebuilt, score=99';
is $da_b->call_count, 2, 'secondary DA called again after timestamp change';
};
# ===========================================================================
# S18: ATTACH with query-time criteria
# The old implementation blocked ATTACH when criteria existed (!%criteria guard).
# The new implementation removes that restriction: ATTACH is unconditional, and
# query-time criteria become SQL WHERE clauses against the ATTACHed table.
# ===========================================================================
subtest 'ATTACH with criteria: zero-copy path used; WHERE filters correctly' => sub {
require DBI;
my $tmpdir = tempdir(CLEANUP => 1);
my $dbfile = File::Spec->catfile($tmpdir, 'attach_crit.db');
my $src_dbh = DBI->connect(
"dbi:SQLite:dbname=$dbfile", '', '',
{ RaiseError => 1, PrintError => 0, AutoCommit => 1 },
);
$src_dbh->do('CREATE TABLE scores (id TEXT, score INTEGER)');
$src_dbh->do("INSERT INTO scores VALUES ('k1', 42)");
$src_dbh->do("INSERT INTO scores VALUES ('k2', 10)");
$src_dbh->do("INSERT INTO scores VALUES ('k3', 55)");
my $da_src = MinimalDA->new(
cols => [qw(id score)],
rows => [
( run in 0.599 second using v1.01-cache-2.11-cpan-7da0f45c409 )