App-Sqitch
view release on metacpan or search on metacpan
Sqitch manages changes and dependencies via a plan file, employing a
[Merkle tree] pattern similar to [Git][gitmerkle] and [Blockchain] to ensure
deployment integrity. As such, there is no need to number your changes,
although you can if you want. Sqitch doesn't much care how you name your
changes.
* Iterative Development
Up until you [tag] and [release] your project, you can modify your change
deployment scripts as often as you like. They're not locked in just because
they've been committed to your VCS. This allows you to take an iterative or
test-driven approach to developing your database schema.
Want to learn more? The best place to start is in the tutorials:
* [Introduction to Sqitch on PostgreSQL, YugabyteDB, and CockroachDB](lib/sqitchtutorial.pod)
* [Introduction to Sqitch on SQLite](lib/sqitchtutorial-sqlite.pod)
* [Introduction to Sqitch on Oracle](lib/sqitchtutorial-oracle.pod)
* [Introduction to Sqitch on MySQL](lib/sqitchtutorial-mysql.pod)
* [Introduction to Sqitch on Firebird](lib/sqitchtutorial-firebird.pod)
lib/App/Sqitch/Engine.pm view on Meta::CPAN
);
# Usually expressed as an integer, but made a number for the purposes of
# shorter test run times.
has lock_timeout => (
is => 'rw',
isa => Num,
default => default_lock_timeout,
);
has _locked => (
is => 'rw',
isa => Bool,
default => 0,
);
has _no_registry => (
is => 'rw',
isa => Bool,
default => 0,
);
lib/App/Sqitch/Engine.pm view on Meta::CPAN
} catch {
$sqitch->info(__ 'not ok');
die $_;
};
}
sub lock_destination {
my $self = shift;
# Try to acquire the lock without waiting.
return $self if $self->_locked;
return $self->_locked(1) if $self->try_lock;
# Lock not acquired. Tell the user what's happening.
my $wait = $self->lock_timeout;
$self->sqitch->info(__x(
'Blocked by another instance of Sqitch working on {dest}; waiting {secs} seconds...',
dest => $self->destination,
secs => $wait,
));
# Try waiting for the lock.
return $self->_locked(1) if $self->wait_lock;
# Timed out, so bail.
hurl engine => __x(
'Timed out waiting {secs} seconds for another instance of Sqitch to finish work on {dest}',
dest => $self->destination,
secs => $wait,
);
}
sub _timeout {
lib/sqitch.pod view on Meta::CPAN
L<Blockchain|https://medium.com/byzantine-studio/blockchain-fundamentals-what-is-a-merkle-tree-d44c529391d7>
to ensure deployment integrity. As such, there is no need to number your
changes, although you can if you want. Sqitch doesn't much care how you name
your changes.
=item Iterative Development
Up until you L<tag|sqitch-tag> and L<release|sqitch-bundle> your project, you
can modify your change deployment scripts as often as you like. They're not
locked in just because they've been committed to your VCS. This allows you to
take an iterative approach to developing your database schema. Or, better, you
can do test-driven database development.
=begin comment
=item Bundling
Rely on your VCS history for deployment but have Sqitch bundle up changes for
distribution. Sqitch can read your VCS history and write out a plan file along
with the appropriate deployment and reversion scripts. Once the bundle is
##############################################################################
# Test lock_destination().
# Test check().
$mock_engine->unmock('lock_destination');
can_ok $engine, 'lock_destination';
is $engine->lock_timeout, 60, 'Lock timeout should be 60 seconds';
# First let the try lock succeed.
$try_lock_ret = 1;
$engine->_locked(0);
ok $engine->lock_destination, 'Lock destination';
is $engine->_locked, 1, 'Should be locked';
is_deeply $engine->seen, [], 'wait_lock should not have been called';
is_deeply +MockOutput->get_info, [], 'Should have emitted no info';
# Now let the lock fail and fall back on waiting for the lock.
$try_lock_ret = 0;
$wait_lock_ret = 1;
$engine->_locked(0);
ok $engine->lock_destination, 'Lock destination';
is $engine->_locked, 1, 'Should be locked again';
is_deeply $engine->seen, ['wait_lock'], 'wait_lock should have been called';
is_deeply +MockOutput->get_info, [[__x(
'Blocked by another instance of Sqitch working on {dest}; waiting {secs} seconds...',
dest => $engine->destination,
secs => $engine->lock_timeout,
)]], 'Should have notified user of waiting for lock';
# Another attempt to lock should be a no-op.
ok $engine->lock_destination, 'Lock destination again';
is_deeply $engine->seen, [], 'wait_lock should not have been called';
is_deeply +MockOutput->get_info, [], 'Should again have emitted no info';
# Now have it time out.
$try_lock_ret = 0;
$wait_lock_ret = 0;
$engine->_locked(0);
$engine->lock_timeout(0.1);
throws_ok { $engine->lock_destination } 'App::Sqitch::X',
'Should get error for lock timeout';
is $@->ident, 'engine', 'Lock timeout error ident should be "engine"';
is $@->exitval, 2, 'Lock timeout error exitval should be 2';
is $@->message, __x(
'Timed out waiting {secs} seconds for another instance of Sqitch to finish work on {dest}',
dest => $engine->destination,
secs => $engine->lock_timeout,
), 'Lock timeout error message should be correct';
is_deeply +MockOutput->get_info, [[__x(
'Blocked by another instance of Sqitch working on {dest}; waiting {secs} seconds...',
dest => $engine->destination,
secs => $engine->lock_timeout,
)]], 'Should have notified user of waiting for lock';
is_deeply $engine->seen, ['wait_lock'], 'wait_lock should have been called';
##############################################################################
# Test _to_idx()
$mock_whu->mock(latest_change_id => 2);
is $engine->_to_idx, $plan->count-1,
'Should get last index when there is a latest change ID';
t/lib/DBIEngineTest.pm view on Meta::CPAN
);
$sth->execute($plan->project);
while (my $row = $sth->fetch) {
is $row->[1], $row->[0],
'Change ID and script hash should be ' . substr $row->[0], 0, 6;
}
######################################################################
# Test try_lock() and wait_lock().
if (my $sql = ($p{lock_sql} || sub {})->($engine)) {
ok !$engine->dbh->selectcol_arrayref($sql->{is_locked})->[0],
'Should not be locked';
ok $engine->try_lock, 'Try lock';
ok $engine->dbh->selectcol_arrayref($sql->{is_locked})->[0],
'Should be locked';
ok $engine->wait_lock, 'Should not have to wait for lock';
# Make a second connection to the database.
my $dbh = DBI->connect($engine->_dsn, $engine->username, $engine->password, {
PrintError => 0,
RaiseError => 0,
AutoCommit => 1,
HandleError => $engine->error_handler,
});
ok !$dbh->selectcol_arrayref($sql->{try_lock})->[0],
'Should fail to get same lock in second connection';
lives_ok { $engine->dbh->do($sql->{free_lock}) } 'Free the lock';
# Wait for the free to complete if frees are async.
if (my $wait = $sql->{async_free}) {
while ($wait) {
$wait = $engine->dbh->selectcol_arrayref($sql->{free_lock})->[0];
}
}
ok !$engine->dbh->selectcol_arrayref($sql->{is_locked})->[0],
'Should not be locked';
ok $dbh->selectcol_arrayref($sql->{try_lock})->[0],
'Should now get the lock in second connection';
ok $engine->dbh->selectcol_arrayref($sql->{is_locked})->[0],
'Should be locked';
ok !$engine->try_lock, 'Try lock should now return false';
# Make sure that wait_lock waits.
my $secs = $sql->{wait_time} || 0.005;
$engine->lock_timeout($secs);
my $time = [gettimeofday];
ok !$engine->wait_lock, 'Should wait and fail to get the lock';
cmp_ok tv_interval($time), '>=', $secs, 'Should have waited for the lock';
lives_ok { $dbh->do($sql->{free_lock}) } 'Free the second lock';
# Wait for the free to complete if frees are async.
if (my $wait = $sql->{async_free}) {
while ($wait) {
$wait = $engine->dbh->selectcol_arrayref($sql->{free_lock})->[0];
}
}
# Now wait lock should acquire the lock.
ok $engine->wait_lock, 'Should no longer wait for lock';
ok $engine->dbh->selectcol_arrayref($sql->{is_locked})->[0],
'Should be locked';
lives_ok { $dbh->do($sql->{free_lock}) } 'Free the lock one last time';
}
######################################################################
# All done.
done_testing;
};
}
sub dt_for_change {
no_zero_date
no_zero_in_date
only_full_group_by
error_for_division_by_zero
)) {
like $sql_mode, qr/\b\Q$mode\E\b/i, "sql_mode should include $mode";
}
},
lock_sql => sub {
my $lock_name = shift->_lock_name; return {
is_locked => "SELECT is_used_lock('$lock_name')",
try_lock => "SELECT get_lock('$lock_name', 0)",
wait_time => 1, # get_lock() does not support sub-second precision, apparently.
async_free => 1,
free_lock => 'SELECT ' . ($dbh ? do {
# MySQL 5.5-5.6 and Maria 10.0-10.4 prefer release_lock(), while
# 5.7+ and 10.5+ prefer release_all_locks().
$dbh->selectrow_arrayref('SELECT version()')->[0] =~ /^(?:5\.[56]|10\.[0-4])/
? "release_lock('$lock_name')"
: 'release_all_locks()'
} : ''),
),
test_dbh => sub {
my $dbh = shift;
# Make sure the sqitch schema is the first in the search path.
is $dbh->selectcol_arrayref('SELECT current_schema')->[0],
$reg2, 'The Sqitch schema should be the current schema';
},
lock_sql => sub {
my $engine = shift;
return {
is_locked => q{SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND objid = 75474063 AND objsubid = 1},
try_lock => 'SELECT pg_try_advisory_lock(75474063)',
free_lock => 'SELECT pg_advisory_unlock_all()',
} if $engine->_provider ne 'yugabyte';
return undef;
},
);
done_testing;
( run in 0.614 second using v1.01-cache-2.11-cpan-49f99fa48dc )