App-Sqitch

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
    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

132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
);
 
# 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

1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
    } 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

82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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

t/engine.t  view on Meta::CPAN

3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
##############################################################################
# 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

1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
        );
        $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 {

t/mysql.t  view on Meta::CPAN

717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
            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()'
        } : ''),

t/pg.t  view on Meta::CPAN

547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
    ),
    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.385 second using v1.01-cache-2.11-cpan-a9ef4e587e4 )