t 000755 001751 000171 0 14316107753 14006 5 ustar 00runner docker 000000 000000 App-Sqitch-v1.3.1 x.t 100644 001751 000171 4424 14316107753 14606 0 ustar 00runner docker 000000 000000 App-Sqitch-v1.3.1/t #!/usr/bin/perl -w use strict; use Test::More; use Test::Exception; use Try::Tiny; use Path::Class; use lib 't/lib'; use TestConfig; my $CLASS; BEGIN { $CLASS = 'App::Sqitch::X'; require_ok $CLASS or die; $CLASS->import(':all'); } isa_ok my $x = $CLASS->new(ident => 'test', message => 'Die'), $CLASS, 'X object'; for my $role(qw( Throwable StackTrace::Auto )) { ok $x->does($role), "X object does $role"; } # Make sure default ident works. ok $x = $CLASS->new(message => 'whatever'), 'Create X without ident'; is $x->ident, 'DEV', 'Default ident should be "DEV"'; throws_ok { hurl basic => 'OMFG!' } $CLASS; isa_ok $x = $@, $CLASS, 'Thrown object'; is $x->ident, 'basic', 'Ident should be "basic"'; is $x->message, 'OMFG!', 'The message should have been passed'; ok $x->stack_trace->frames, 'It should have a stack trace'; is $x->exitval, 2, 'Exit val should be 2'; is +($x->stack_trace->frames)[0]->filename, file(qw(t x.t)), 'The trace should start in this file'; # NB: Don't use `local $@`, as it does not work on Perls < 5.14. throws_ok { $@ = 'Yo dawg'; hurl 'OMFG!' } $CLASS; isa_ok $x = $@, $CLASS, 'Thrown object'; is $x->ident, 'DEV', 'Ident should be "DEV"'; is $x->message, 'OMFG!', 'The message should have been passed'; is $x->exitval, 2, 'Exit val should again be 2'; is $x->previous_exception, 'Yo dawg', 'Previous exception should have been passed'; throws_ok { hurl {ident => 'blah', message => 'OMFG!', exitval => 1} } $CLASS; isa_ok $x = $@, $CLASS, 'Thrown object'; is $x->message, 'OMFG!', 'The params should have been passed'; is $x->exitval, 1, 'Exit val should be 1'; is $x->as_string, join("\n", grep { defined } $x->message, $x->previous_exception, $x->stack_trace ), 'Stringification should work'; is $x->as_string, "$x", 'Stringification should work'; # Do some actual exception handling. try { hurl io => 'Cannot open file'; } catch { return fail "Not a Sqitch::X: $_" unless eval { $_->isa('App::Sqitch::X') }; is $_->ident, 'io', 'Should be an "io" exception'; }; # Make sure we can goto hurl. try { @_ = (io => 'Cannot open file'); goto &hurl; } catch { return fail "Not a Sqitch::X: $_" unless eval { $_->isa('App::Sqitch::X') }; is $_->ident, 'io', 'Should catch error called via &goto'; }; done_testing; pg.t 100644 001751 000171 50135 14316107753 14765 0 ustar 00runner docker 000000 000000 App-Sqitch-v1.3.1/t #!/usr/bin/perl -w # There are two ways to test against a live Postgres database. If there is an # instance on the local host trusting all local socket connections and the # default postgres user, the test will connect, create the database it needs, # run the tests, and then drop the database. # # Alternatively, provide the URL to connect to a Postgres database in the # SQITCH_TEST_PG_URI environment variable. this is a standard URI::db URI, and # should look something like this: # # export SQITCH_TEST_PG_URI=db:pg://postgres:password@localhost:5432/sqitchtest # # It should use the C locale (`ALTER DATABASE $db SET lc_messages = 'C'`) to # ensure proper sorting while testing. Sqitch will connect to this database and # create two schemas to run the tests in, `sqitch` and `__sqitchtest`, and will # drop them when the tests complete. # use strict; use warnings; use 5.010; use Test::More 0.94; use Test::MockModule; use Test::Exception; use Test::File::Contents; use Locale::TextDomain qw(App-Sqitch); use Capture::Tiny 0.12 qw(:all); use Try::Tiny; use App::Sqitch; use App::Sqitch::Target; use App::Sqitch::Plan; use Path::Class; use DBD::Mem; use lib 't/lib'; use DBIEngineTest; use TestConfig; my $CLASS; BEGIN { $CLASS = 'App::Sqitch::Engine::pg'; require_ok $CLASS or die; delete $ENV{PGPASSWORD}; } is_deeply [$CLASS->config_vars], [ target => 'any', registry => 'any', client => 'any', ], 'config_vars should return three vars'; my $uri = URI::db->new('db:pg:'); my $config = TestConfig->new('core.engine' => 'pg'); my $sqitch = App::Sqitch->new(config => $config); my $target = App::Sqitch::Target->new( sqitch => $sqitch, uri => $uri, ); isa_ok my $pg = $CLASS->new(sqitch => $sqitch, target => $target), $CLASS; is $pg->key, 'pg', 'Key should be "pg"'; is $pg->name, 'PostgreSQL', 'Name should be "PostgreSQL"'; my $client = 'psql' . (App::Sqitch::ISWIN ? '.exe' : ''); is $pg->client, $client, 'client should default to psqle'; is $pg->registry, 'sqitch', 'registry default should be "sqitch"'; is $pg->uri, $uri, 'DB URI should be "db:pg:"'; my $dest_uri = $uri->clone; $dest_uri->dbname($ENV{PGDATABASE} || $ENV{PGUSER} || $sqitch->sysuser); is $pg->destination, $dest_uri->as_string, 'Destination should fall back on environment variables'; is $pg->registry_destination, $pg->destination, 'Registry destination should be the same as destination'; my @std_opts = ( '--quiet', '--no-psqlrc', '--no-align', '--tuples-only', '--set' => 'ON_ERROR_STOP=1', '--set' => 'registry=sqitch', ); my $sysuser = $sqitch->sysuser; is_deeply [$pg->psql], [$client, @std_opts], 'psql command should be conninfo, and std opts-only'; isa_ok $pg = $CLASS->new(sqitch => $sqitch, target => $target), $CLASS; ok $pg->set_variables(foo => 'baz', whu => 'hi there', yo => 'stellar'), 'Set some variables'; is_deeply [$pg->psql], [ $client, '--set' => 'foo=baz', '--set' => 'whu=hi there', '--set' => 'yo=stellar', @std_opts, ], 'Variables should be passed to psql via --set'; ############################################################################## # Test other configs for the target. ENV: { # Make sure we override system-set vars. local $ENV{PGDATABASE}; for my $env (qw(PGDATABASE PGUSER PGPASSWORD)) { my $pg = $CLASS->new(sqitch => $sqitch, target => $target); local $ENV{$env} = "\$ENV=whatever"; is $pg->target->uri, "db:pg:", "Target should not read \$$env"; is $pg->registry_destination, $pg->destination, 'Registry target should be the same as destination'; } my $mocker = Test::MockModule->new('App::Sqitch'); $mocker->mock(sysuser => 'sysuser=whatever'); my $pg = $CLASS->new(sqitch => $sqitch, target => $target); is $pg->target->uri, 'db:pg:', 'Target should not fall back on sysuser'; is $pg->registry_destination, $pg->destination, 'Registry target should be the same as destination'; $ENV{PGDATABASE} = 'mydb'; $pg = $CLASS->new(sqitch => $sqitch, username => 'hi', target => $target); is $pg->target->uri, 'db:pg:', 'Target should be the default'; is $pg->registry_destination, $pg->destination, 'Registry target should be the same as destination'; } ############################################################################## # Make sure config settings override defaults. $config->update( 'engine.pg.client' => '/path/to/psql', 'engine.pg.target' => 'db:pg://localhost/try?sslmode=disable&connect_timeout=5', 'engine.pg.registry' => 'meta', ); $std_opts[-1] = 'registry=meta'; $target = App::Sqitch::Target->new( sqitch => $sqitch ); ok $pg = $CLASS->new(sqitch => $sqitch, target => $target), 'Create another pg'; is $pg->client, '/path/to/psql', 'client should be as configured'; is $pg->uri->as_string, 'db:pg://localhost/try?sslmode=disable&connect_timeout=5', 'uri should be as configured'; is $pg->registry, 'meta', 'registry should be as configured'; is_deeply [$pg->psql], [ '/path/to/psql', '--dbname', "dbname=try host=localhost connect_timeout=5 sslmode=disable", @std_opts], 'psql command should be configured from URI config'; ############################################################################## # Test _run(), _capture(), _spool(), and _probe(). can_ok $pg, qw(_run _capture _spool _probe); my $mock_sqitch = Test::MockModule->new('App::Sqitch'); my (@run, $exp_pass); $mock_sqitch->mock(run => sub { local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @run = @_; if (defined $exp_pass) { is $ENV{PGPASSWORD}, $exp_pass, qq{PGPASSWORD should be "$exp_pass"}; } else { ok !exists $ENV{PGPASSWORD}, 'PGPASSWORD should not exist'; } }); my @capture; $mock_sqitch->mock(capture => sub { local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @capture = @_; if (defined $exp_pass) { is $ENV{PGPASSWORD}, $exp_pass, qq{PGPASSWORD should be "$exp_pass"}; } else { ok !exists $ENV{PGPASSWORD}, 'PGPASSWORD should not exist'; } }); my @spool; $mock_sqitch->mock(spool => sub { local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @spool = @_; if (defined $exp_pass) { is $ENV{PGPASSWORD}, $exp_pass, qq{PGPASSWORD should be "$exp_pass"}; } else { ok !exists $ENV{PGPASSWORD}, 'PGPASSWORD should not exist'; } }); my @probe; $mock_sqitch->mock(probe => sub { local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @probe = @_; if (defined $exp_pass) { is $ENV{PGPASSWORD}, $exp_pass, qq{PGPASSWORD should be "$exp_pass"}; } else { ok !exists $ENV{PGPASSWORD}, 'PGPASSWORD should not exist'; } }); $target->uri->password('s3cr3t'); $exp_pass = 's3cr3t'; ok $pg->_run(qw(foo bar baz)), 'Call _run'; is_deeply \@run, [$pg->psql, qw(foo bar baz)], 'Command should be passed to run()'; ok $pg->_spool('FH'), 'Call _spool'; is_deeply \@spool, ['FH', $pg->psql], 'Command should be passed to spool()'; ok $pg->_capture(qw(foo bar baz)), 'Call _capture'; is_deeply \@capture, [$pg->psql, qw(foo bar baz)], 'Command should be passed to capture()'; ok $pg->_probe(qw(hi there)), 'Call _probe'; is_deeply \@probe, [$pg->psql, qw(hi there)]; # Without password. $target = App::Sqitch::Target->new( sqitch => $sqitch ); ok $pg = $CLASS->new(sqitch => $sqitch, target => $target), 'Create a pg with sqitch with no pw'; $exp_pass = undef; ok $pg->_run(qw(foo bar baz)), 'Call _run again'; is_deeply \@run, [$pg->psql, qw(foo bar baz)], 'Command should be passed to run() again'; ok $pg->_spool('FH'), 'Call _spool again'; is_deeply \@spool, ['FH', $pg->psql], 'Command should be passed to spool() again'; ok $pg->_capture(qw(foo bar baz)), 'Call _capture again'; is_deeply \@capture, [$pg->psql, qw(foo bar baz)], 'Command should be passed to capture() again'; ok $pg->_probe(qw(go there)), 'Call _probe again'; is_deeply \@probe, [$pg->psql, qw(go there)]; ############################################################################## # Test file and handle running. ok $pg->run_file('foo/bar.sql'), 'Run foo/bar.sql'; is_deeply \@run, [$pg->psql, '--file', 'foo/bar.sql'], 'File should be passed to run()'; ok $pg->run_handle('FH'), 'Spool a "file handle"'; is_deeply \@spool, ['FH', $pg->psql], 'Handle should be passed to spool()'; # Verify should go to capture unless verosity is > 1. ok $pg->run_verify('foo/bar.sql'), 'Verify foo/bar.sql'; is_deeply \@capture, [$pg->psql, '--file', 'foo/bar.sql'], 'Verify file should be passed to capture()'; $mock_sqitch->mock(verbosity => 2); ok $pg->run_verify('foo/bar.sql'), 'Verify foo/bar.sql again'; is_deeply \@run, [$pg->psql, '--file', 'foo/bar.sql'], 'Verifile file should be passed to run() for high verbosity'; $mock_sqitch->unmock_all; ############################################################################## # Test DateTime formatting stuff. ok my $ts2char = $CLASS->can('_ts2char_format'), "$CLASS->can('_ts2char_format')"; is sprintf($ts2char->($pg), 'foo'), q{to_char(foo AT TIME ZONE 'UTC', '"year":YYYY:"month":MM:"day":DD:"hour":HH24:"minute":MI:"second":SS:"time_zone":"UTC"')}, '_ts2char_format should work'; ok my $dtfunc = $CLASS->can('_dt'), "$CLASS->can('_dt')"; isa_ok my $dt = $dtfunc->( 'year:2012:month:07:day:05:hour:15:minute:07:second:01:time_zone:UTC' ), 'App::Sqitch::DateTime', 'Return value of _dt()'; is $dt->year, 2012, 'DateTime year should be set'; is $dt->month, 7, 'DateTime month should be set'; is $dt->day, 5, 'DateTime day should be set'; is $dt->hour, 15, 'DateTime hour should be set'; is $dt->minute, 7, 'DateTime minute should be set'; is $dt->second, 1, 'DateTime second should be set'; is $dt->time_zone->name, 'UTC', 'DateTime TZ should be set'; ############################################################################## # Test _psql_major_version. for my $spec ( ['11beta3', 11], ['11.3', 11], ['10', 10], ['9.6.3', 9], ['8.4.2', 8], ['9.0.19', 9], ) { $mock_sqitch->mock(probe => "psql (PostgreSQL) $spec->[0]"); is $pg->_psql_major_version, $spec->[1], "Should find major version $spec->[1] in $spec->[0]"; } $mock_sqitch->unmock('probe'); ############################################################################## # Test table error and listagg methods. DBI: { local *DBI::state; ok !$pg->_no_table_error, 'Should have no table error'; ok !$pg->_no_column_error, 'Should have no column error'; $DBI::state = '42703'; ok !$pg->_no_table_error, 'Should again have no table error'; ok $pg->_no_column_error, 'Should now have no column error'; # Need to mock DBH for table errors. my $dbh = DBI->connect('dbi:Mem:', undef, undef, {}); my $mock_engine = Test::MockModule->new($CLASS); $mock_engine->mock(dbh => $dbh); my $mock_dbd = Test::MockModule->new(ref $dbh, no_auto => 1); $mock_dbd->mock(quote => sub { qq{'$_[1]'} }); my @done; $mock_dbd->mock(do => sub { shift; @done = @_ }); # Should just work when on 8.4. $DBI::state = '42P01'; $dbh->{pg_server_version} = 80400; ok $pg->_no_table_error, 'Should now have table error'; ok !$pg->_no_column_error, 'Still should have no column error'; is_deeply \@done, [], 'No SQL should have been run'; # On 9.0 and later, we should send warnings to the log. $dbh->{pg_server_version} = 90000; ok $pg->_no_table_error, 'Should again have table error'; ok !$pg->_no_column_error, 'Still should have no column error'; is_deeply \@done, [sprintf q{DO $$ BEGIN SET LOCAL client_min_messages = 'ERROR'; RAISE WARNING USING ERRCODE = 'undefined_table', MESSAGE = %s, DETAIL = %s; END; $$}, map { "'$_'" } __ 'Sqitch registry not initialized', __ 'Because the "changes" table does not exist, Sqitch will now initialize the database to create its registry tables.', ], 'Should have sent an error to the log'; # Test _listagg_format. $dbh->{pg_server_version} = 110000; is $pg->_listagg_format, q{array_remove(array_agg(%1$s ORDER BY %1$s), NULL)}, 'Should use array_remove and ORDER BY in listagg_format on v11'; $dbh->{pg_server_version} = 90300; is $pg->_listagg_format, q{array_remove(array_agg(%1$s ORDER BY %1$s), NULL)}, 'Should use array_remove and ORDER BY in listagg_format on v9.3'; $dbh->{pg_server_version} = 90200; is $pg->_listagg_format, q{ARRAY(SELECT * FROM UNNEST( array_agg(%1$s ORDER BY %1$s) ) a WHERE a IS NOT NULL)}, 'Should use ORDER BY in listagg_format on v9.2'; $dbh->{pg_server_version} = 80400; is $pg->_listagg_format, q{ARRAY(SELECT * FROM UNNEST( array_agg(%1$s ORDER BY %1$s) ) a WHERE a IS NOT NULL)}, 'Should use ORDER BY in listagg_format on v8.4'; $dbh->{pg_server_version} = 80300; is $pg->_listagg_format, q{ARRAY(SELECT * FROM UNNEST( array_agg(%s) ) a WHERE a IS NOT NULL)}, 'Should not use ORDER BY in listagg_format on v8.3'; } ############################################################################## # Test _run_registry_file. RUNREG: { # Mock I/O used by _run_registry_file. my $mock_engine = Test::MockModule->new($CLASS); my (@probed, @prob_ret); $mock_engine->mock(_probe => sub { shift; push @probed, \@_; shift @prob_ret; }); my $psql_maj; $mock_engine->mock(_psql_major_version => sub { $psql_maj }); my @ran; $mock_engine->mock(_run => sub { shift; push @ran, \@_ }); # Mock up the database handle. my $dbh = DBI->connect('dbi:Mem:', undef, undef, {}); $mock_engine->mock(dbh => $dbh ); my $mock_dbd = Test::MockModule->new(ref $dbh, no_auto => 1); my @done; $mock_dbd->mock(do => sub { shift; push @done, \@_; 1 }); my @sra_args; $mock_dbd->mock(selectrow_array => sub { shift; push @sra_args, [@_]; return (qq{"$_[-1]"}); }); # Mock File::Temp so we hang on to the file. my $mock_ft = Test::MockModule->new('File::Temp'); my $tmp_fh; my $ft_new; $mock_ft->mock(new => sub { $tmp_fh = 'File::Temp'->$ft_new() }); $ft_new = $mock_ft->original('new'); # Find the SQL file. my $ddl = file($INC{'App/Sqitch/Engine/pg.pm'})->dir->file('pg.sql'); # The XC query. my $xc_query = q{ SELECT count(*) FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid WHERE nspname = 'pg_catalog' AND proname = 'pgxc_version'; }; # Start with a recent version and no XC. $psql_maj = 11; @prob_ret = (110000, 0); my $registry = $pg->registry; ok $pg->_run_registry_file($ddl), 'Run the registry file'; is_deeply \@probed, [ ['-c', 'SHOW server_version_num'], ['-c', $xc_query], ], 'Should have fetched the server version and checked for XC'; is_deeply \@ran, [[ '--file' => $ddl, '--set' => "registry=$registry", '--set' => "tableopts=", ]], 'Shoud have deployed the original SQL file'; is_deeply \@done, [['SET search_path = ?', undef, $registry]], 'The registry should have been added to the search path'; is_deeply \@sra_args, [], 'Should not have have called selectrow_array'; is $tmp_fh, undef, 'Should have no temp file handle'; # Reset and try Postgres 9.2 server @probed = @ran = @done = (); $psql_maj = 11; @prob_ret = (90200, 1); ok $pg->_run_registry_file($ddl), 'Run the registry file again'; is_deeply \@probed, [ ['-c', 'SHOW server_version_num'], ['-c', $xc_query], ], 'Should have again fetched the server version and checked for XC'; isnt $tmp_fh, undef, 'Should now have a temp file handle'; is_deeply \@ran, [[ '--file' => $tmp_fh, '--set' => "tableopts= DISTRIBUTE BY REPLICATION", ]], 'Shoud have deployed the temp SQL file'; is_deeply \@sra_args, [], 'Still hould not have have called selectrow_array'; is_deeply \@done, [['SET search_path = ?', undef, $registry]], 'The registry should have been added to the search path again'; # Make sure the file was changed to remove SCHEMA IF NOT EXISTS. file_contents_like $tmp_fh, qr/\QCREATE SCHEMA :"registry";/, 'Should have removed IF NOT EXISTS from CREATE SCHEMA'; # Reset and try with Server 11 and psql 8.x. @probed = @ran = @done = (); $psql_maj = 8; $tmp_fh = undef; @prob_ret = (110000, 0); ok $pg->_run_registry_file($ddl), 'Run the registry file again'; is_deeply \@probed, [ ['-c', 'SHOW server_version_num'], ['-c', $xc_query], ], 'Should have again fetched the server version and checked for XC'; isnt $tmp_fh, undef, 'Should now have a temp file handle'; is_deeply \@ran, [[ '--file' => $tmp_fh, '--set' => "tableopts=", ]], 'Shoud have deployed the temp SQL file'; is_deeply \@sra_args, [['SELECT quote_ident(?)', undef, $registry]], 'Should have have called quote_ident via selectrow_array'; is_deeply \@done, [['SET search_path = ?', undef, qq{"$registry"}]], 'The registry should have been added to the search path again'; file_contents_like $tmp_fh, qr/\QCREATE SCHEMA IF NOT EXISTS "$registry";/, 'Should not have removed IF NOT EXISTS from CREATE SCHEMA'; file_contents_unlike $tmp_fh, qr/:"registry"/, 'Should have removed the :"registry" variable'; } ############################################################################## # Can we do live tests? $config->replace('core.engine' => 'pg'); $sqitch = App::Sqitch->new(config => $config); $target = App::Sqitch::Target->new( sqitch => $sqitch ); $pg = $CLASS->new(sqitch => $sqitch, target => $target); $uri = URI->new( $ENV{SQITCH_TEST_PG_URI} || 'db:pg://' . ($ENV{PGUSER} || 'postgres') . "\@/template1" ); my $dbh; my $id = DBIEngineTest->randstr; my ($db, $reg1, $reg2) = map { $_ . $id } qw(__sqitchtest__ sqitch __sqitchtest); END { return unless $dbh; $dbh->{Driver}->visit_child_handles(sub { my $h = shift; $h->disconnect if $h->{Type} eq 'db' && $h->{Active} && $h ne $dbh; }); # Drop the database or schema. if ($dbh->{Active}) { if ($ENV{SQITCH_TEST_PG_URI}) { $dbh->do('SET client_min_messages = warning'); $dbh->do("DROP SCHEMA $_ CASCADE") for $reg1, $reg2; } else { $dbh->do("DROP DATABASE $db"); } } } my $err = try { $pg->_capture('--version'); $pg->use_driver; $dbh = DBI->connect($uri->dbi_dsn, $uri->user, $uri->password, { PrintError => 0, RaiseError => 1, AutoCommit => 1, pg_lc_messages => 'C', }); unless ($ENV{SQITCH_TEST_PG_URI}) { $dbh->do("CREATE DATABASE $db"); $uri->dbname($db); } undef; } catch { eval { $_->message } || $_; }; DBIEngineTest->run( class => $CLASS, version_query => 'SELECT version()', target_params => [ uri => $uri, registry => $reg1 ], alt_target_params => [ uri => $uri, registry => $reg2 ], skip_unless => sub { my $self = shift; die $err if $err; # Make sure we have psql and can connect to the database. my $version = $self->sqitch->capture( $self->client, '--version' ); say "# Detected $version"; $self->_capture('--command' => 'SELECT version()'); }, engine_err_regex => qr/^ERROR: /, init_error => __x( 'Sqitch schema "{schema}" already exists', schema => $reg2, ), 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; App-Sqitch-v1.3.1 000755 001751 000171 0 14316107753 13622 5 ustar 00runner docker 000000 000000 README 100644 001751 000171 503 14316107753 14541 0 ustar 00runner docker 000000 000000 App-Sqitch-v1.3.1 This archive contains the distribution App-Sqitch, version v1.3.1: Sensible database change management This software is Copyright (c) 2012-2022 by "iovation Inc., David E. Wheeler". This is free software, licensed under: The MIT (X11) License This README file was generated by Dist::Zilla::Plugin::Readme v6.025. Changes 100644 001751 000171 362073 14316107753 15251 0 ustar 00runner docker 000000 000000 App-Sqitch-v1.3.1 Revision history for Perl extension App::Sqitch 1.3.1 2022-10-01T18:49:30Z - Fixed a bug introduced in v1.3.0 where the Postgres engine would always pass the port to `psql`, thus ignoring the `PGPORT` environment variable. Thanks to Cam Feenstra for the spot (#675)! - Fixed test failures on OSes where the local time zone cannot be determined. Thanks to Slaven Rezić for the test reports, and to Dave Rolsky for the solution (#672). - Updated the MySQL deploy/revert lock to be specific to the target database. This allows multiple instances of Sqitch to run at the same time on the same server as long as they're connecting to different databases. Thanks to Dmytro Kh for the report and discussion of the options (#670). - Fixed test failures where DBD::Mem was not installed. Likely only occurred on some CPAN Testers nodes. Thanks to Slaven Rezić for those (#673). - Banned the backslash character (`\`) in change and tag names. It would be ignored on Unix-style systems, but create unexpected subdirectories on Windows systems. - Banned the slash character (`/`) in tag names. They're still allowed in change names to enable script organization, but can wreak havoc when used in tag names. Thanks to @ewie for the report (#680)! 1.3.0 2022-08-12T22:09:13Z - Fixed an issue when testing Firebird on a host with Firebird installed but no `isql`, and when using a local Firebird (e.g., the Engine12 provider), which allows only one connection at a time. Thanks to Slaven Rezić for the the reproducible configuration (#597). - Tweaked the Postgres engine to support Yugabyte. The only unsupported features are explicit locks, so users need to manually ensure that only one instance of Sqitch is updating the cluster at a time. - Added support for CockroachDB. Almost exactly the same as for Postgres, so the new App::Sqitch::Engine::cockroach class extends App::Sqitch::Engine::pg to make a few changes. The SQL files with the registry DDL varies in a few ways, so they're separate. - Now require URI::db v0.20 for Cockroach and Yugabyte URI support. - Dropped support for MySQL 5.0. - Added explicit sorting for aggregated lists (such as the tags associated with a commit) to the MySQL, Exasol, Snowflake, and Postgres (8.4 and higher) engines. - Fixed slow deploys on MariaDB thanks to fractional timestamp support added in 5.03.05. Thanks to @rbrigot for the PR (#658)! - Fixed a bug where destination locking failed on the first deploy to MySQL. Bug introduced along with destination locking in v1.2.0. Thanks Tom Bloor the report and to Alberto Simões for the help replicating the issue (#601). - Removed the `sqitch engine update-config` action, originally added for compatibility reasons in 2014, and the prompt to use it was removed as of 0.9999 in 2019. - Fixed a warning when searching for the Firebird client on Windows. 1.2.1 2021-12-05T19:59:45Z - Updated all the live engine tests, aside from Oracle, to test with unique registry names, so as to avoid conflicts when multiple instances are being tested at once. Thanks to Slaven Rezić for the report (#597). - Removed `local` directory from the distribution, accidentally included in v1.2.0. Thanks to gregor herrmann for the report (#600). 1.2.0 2021-11-20T22:45:00Z - Fixed partitioned search for deployed changes on Oracle and Exasol to correctly support plans with more than 250 changes. Thanks to @Nicqu for the report (#521). - DBI connections to the registry will now be set to trace level one when a verbosity level of three or higher is passed to Sqitch (i.e., `sqitch -vvv`). Thanks to @wkoszek for the suggestion (#155). - Renamed the "master" branch to "main" and updated all relevant references. - Fixed the parsing of the Snowflake account name from the target URL to that it no longer strips out the region and cloud platform parts. Also deprecated the use of the region, host, and port config and environment variables. This is to comply with the new account name format. Thanks to @ldsingh00 for the report (#544). - The checkout command will now show a usage statement when no branch name is passed to it, rather than a series of warnings and a Git error. Thanks to François Beausoleil for the report (#561). - The checkout command now works when operating on a Sqitch project in a subdirectory of a Git project. Thanks to François Beausoleil for the report and suggested fix (#560). - Fixed a failing bundle test when a top directory was configured in the user or system configuration. Thanks to @CodingMinds for the spot (#587). - Added support to the Exasol engine for the `AUTHMETHOD` query parameter (to allow Open ID authentication) and the `SSLCERTIFICATE=SSL_VERIFY_NONE` query parameter to disable SSL verification. Thanks to Torsten Glunde for the report (#588). - Fixed "Use of uninitialized value $engine in concatenation" error when Sqitch is unable to determine the engine when writing a plan file. Only happens in the rare case that the core.engine value is not set. - Improved the error message when attempting to update a plan file and no project pragma is present. Thanks to Laurentiu Diaconu for the report (#577). - Fixed the error message when attempting to deploy a change that has already been deployed to display the name of the change, rather than a memory address. Thanks to Neil Mayhew for the report (#579). - Added destination locking, currently implemented for PostgresQL and MySQL. On starting a deploy or revert, Sqitch attempts to "lock the destination" using advisory locks, to ensure that only one instance of Sqitch makes changes to the database at any one time. This complements the existing locking, which applies as each change is deployed or reverted, as that pattern led to failures when multiple instances of Sqitch were working at once. Thanks to Neil Mayhew for the report (#579). - Added the --lock-timeout option to the deploy, revert, rebase, and checkout commands, to allow for shorter or longer times to wait for an exclusive target database advisory lock for engines that support it. Defaults to 60 seconds. - Fixed the behavior of the plan command's `--max-count` option when used with `--reverse` to show the proper items. Thanks to Adrian Klaver for the report (#517). - Fixed an issue that could cause bugs with the `check` command on Firebird, Exasol, Oracle, and Vertica. Broken since the `check` command was added in v1.1.0. 1.1.0 2020-05-17T16:20:07Z - Fixed Perl Pod errors, thanks to a pull request from Mohammad S Anwar (#470). - Fixed test failures when running with the localization set to German or Italian. Thanks to Slaven Rezić for the report (#472). - Fixed an issue when the full name of the current user is not set, so that it properly falls back on the username. Thanks to Slaven Rezić and Matthieu Foucault for the report and testing various fixes (#473). - Eliminated an error when using the `-t` option to specify a target, due to a missing dependency declaration in the Target class. Thanks to Clinton Adams for the fix (#509)! - Updated the IPC::System::Simple Win32 workaround added in 0.9999 to properly support released versions of IPC::System::Simple. This fixes errors running the database command-line clients on Windows (#503). - Sqitch now only passes the `--password` option to the MySQL client if it was not read from the `.my.cnf` file, as it's more secure to let the client use `.my.cnf`, and it eliminates a warning from recent versions of the client. Thanks to Kiel R Stirling for the fix (#485)! - Added a note to the tutorials to skip setting the `engine.$engine.client` config when using the Docker image. - Added the new `check` command, which compares the SHA1 hashes of the deploy scripts to the database, and reports if any have been modified since they were deployed. Thanks to Matthieu Foucault for the pull request and diligent work on this feature (#477)! - Added the `--modified` option to the `rebase` and `revert` commands, to revert to the earliest change with a modified deploy script. Handy for rapid rebasing during iterative development cycles. Thanks to Matthieu Foucault for this feature (#477)! - Fixed an issue where the Snowflake engine would complain about not finding the account name even for commands that don't need them, such as `init`. Thanks to Stack Overflow user vbp13 for the report (#502). 1.0.0 2019-06-04T12:56:22Z - Fixed test failure due to a hard-coded system error that may be localized on non-en-US hosts. Thanks to Slaven Rezić for the catch (#427). - Now require Test::MockModule 0.17 to silence a warning during testing. Thanks to Slaven Rezić for the suggestion. - Fixed an error when Sqitch is run with no arguments. Thanks to Henrik Tudborg for the report (#428). - Fixed missing dependency on IO::Pager in the distribution metadata. - Removed use of File::HomeDir, thanks to a PR from Karen Etheridge (#433). - Updated the tagline from "Sane database change management" to "Sensible database change management" out of sensitivity to those subject to mental illness (#435). - Removed double-quoting of SQLite commands on Windows, inadvertently added by the workaround for Windows quoting in v0.9999. - Fixed a Snowflake issue where Sqitch failed to recognize the proper error code for a missing table and therefore an uninitialized registry. Thanks to @lerouxt and @kulmam92 for the report and fix (#439). - Added check for project initialization when no engine config can be found. When run from a directory with no configuration, Sqitch now reports that the project is not initialized instead of complaining about a lack of engine config (#437). - Documented Snowflake key pair authentication in `sqitch-authentication`, as well as `$SNOWSQL_PRIVATE_KEY_PASSPHRASE` in `sqitch-environment`. Thanks to Casey Largent for figuring it out (#441). - Added the German localization. Thanks to Thomas Iguchi for the pull request (#451). - Renamed the French localization from "fr" to "fr_FR", so that systems will actually find it. - Added the `ask_yes_no()` method as a replacement for `ask_y_n()`, which is now deprecated. The new method expects localized responses from the user when translations are provided. Defaults to the English "yes" and "no" when no translation is available. Suggested by German translator Thomas Iguchi (#449). - Fixed a bug where only project without a URI was allowed in the registry. Thanks to Conding-Brunna for the report (#450). - Clarified the role of project URIs for uniqueness: They don't allow multiple projects with the same name, but do prevent the deployment of a project with the same name but different URI. - Fixed an issue where target variables could not be found when a target name was not lowercase. Thanks to @maximejanssens for the report (#454). - Now require Config::GitLike 1.15 or higher. - Fixed the indentation of variables emitted by the `show` actions of the `target` and `engine` commands, fixing a "Negative repeat count does nothing" warning in the process. Thanks to @maximejanssens for the report (#454). - Fixed a Snowflake test failure when the current system username has a space or other character requiring URI escaping. Thanks to Ralph Andrade for the report (#463). - Fixed an issue where a wayward newline in some versions of SQLite prevented Sqitch from parsing the version. Thanks to Kivanc Yazan for the report (#465) and the fix (#465)! - Fixed an error when Sqitch was run on a system without a valid username, such as some Docker environments. Thanks to Ferdinand Salis for the report (#459)! - When Sqitch finds the registry does not exist on PostgreSQL, it now sends a warning to the PostgreSQL log reporting that it will initialize the database. This is to reduce confusion for folks watching the PostgreSQL error log while Sqitch runs (#314). 0.9999 2019-02-01T15:29:40Z [Bug Fixes] - Fixed a test failure with the MySQL max limit value, mostly exhibited on BSD platforms. - Removed fallback in the PostgreSQL engine on the `$PGUSER` and `$PGPASSWORD` environnement variables, as well as the system username, since libpq does all that automatically, and collects data from other sources that we did not (e.g., the password and connection service files). Thanks to Tom Bloor for the report (issue #410). - Changed dependency validation to prevent an error when a change required from a different project has been reworked. Previously, when requiring a change such as `foo:greeble`, Sqitch would raise an error if `foo:greeble` was reworked, suggesting that the dependency be tag-qualified to eliminate ambiguity. Now reworked dependencies may be required without tag-qualification, though tag-qualification should still be specified if functionality as of a particular tag is required. - Added a workaround for the shell quoting issue on Windows. Applies to IPC::System::Simple 1.29 and lower. See [pjf/ipc-system-simple#29](https://github.com/pjf/ipc-system-simple/pull/29) for details (#413). - Fixed an issue with the MariaDB client where a deploy, revert, or verify failure was not properly propagated to Sqitch. Sqitch now passes `--abort-source-on-error` to the Maria `mysql` client to ensure that SQL errors cause the client to abort with an error so that Sqitch can properly handle it. Thanks to @mvgrimes for the original report and, years later, the fix (#209). - Fixed an issue with command argument parsing so that it truly never returns a target without an engine specified, as documented. - Removed documentation for methods that don't exist. - Fixed test failures due to a change in Encode v2.99 that's stricter about `undef` arguments that should be defined. [Improvements] - The Snowflake engine now consults the `connections.warehousename`, `connections.dbname`, and `connections.rolename` variables in the SnowSQL configuration file (`~/.snowsql/config`) before falling back on the hard-coded warehouse name "sqitch" and using the system username as the database name and no default for the role. - Switched to using a constant internally to optimize windows-specific code paths at compile time. - When `deploy` detects undeployed dependencies, it now eliminates duplicates before listing them in the error message. - Now requiring IO::Pager v0.34 or later for its more consistent interface. - Added notes about creating databases to the tutorials. Thanks to Dave Rolsky for the prompt (#315). - Added a status message to tell the user when the registry is being updated, rather than just show each individual update. Thanks to Ben Hutton for the suggestion (#276). - Added support for a `$SQITCH_TARGET` environment variable, which takes precedence over all other target specifications except for command-line options and arguments. Thanks to @mvgrimes for the suggestion (#203). - Fixed target/engine/change argument parsing so it won't automatically fail when `core.engine` isn't set unless no targets are found. This lets engines be determined strictly from command-line arguments -- derived from targets, or just listed on their own -- whether or not `core.engine` is set. This change eliminates the need for the `no_default` parameter to the `parse_args()` method of App::Sqitch Command. It also greatly reduces the need for the core `--engine` option, which was previously required to work around this issue (see below for its removal). - Refactored config handling in tests to use a custom subclass of App::Sqitch::Config instead of various mocks, temporary files, and the like. - Added advice to use the PL/pgSQL `ASSERT()` function for verify scripts to the Postgres tutorial. Thanks to Sergii Tkachenko for the PR (#425). [Target Variables] - The `verify` command now reads `deploy.variables`, and individual `verify.variables override `deploy.variables`, on the assumption that the verify variables in general ought to be the same as the deploy variables. This makes `verify` variable configuration consistent with `revert` variable configuration. - Variables set via the `--set-deploy` option on the `rebase` and `checkout` commands no longer apply to both reverts and deploys, but only deploys. Use the `--set` option to apply a variable to both reverts and deploys. - Added support for core, engine, and target variable configuration. The simplest way to use them is via the `--set` option on the `init`, `engine`, and `target` commands. These commands allow the configuration of database client variables for specific engines and targets, as well as defaults that apply to all change execution commands (`deploy`, `revert`, `verify`, `rebase`, and `checkout`). The commands merge the variables from each level in this priority order: * `--set-deploy` and `--set-revert` options on `rebase` and `checkout` * `--set` option * `target.$target.variables` * `engine.$engine.variables` * `deploy.variables`, `revert.variables`, and `verify.variables` * `core.variables` See `sqitch-configuration` for general documentation of of the hierarchy for merging variables and the documentation for each command for specifics. [Options Unification] - Added the `--chdir`/`--cd`/`-C` option to specify a directory to change to before executing any Sqitch commands. Thanks to Thomas Sibley for the suggestion (#411). - Added the `--no-pager` option to disable the pager (#414). - Changed command-line parsing to allow core and command options to appear anywhere on the line. Previously, core options had to come before the command name, and command options after. No more. The caveat is that command options that take arguments should either appear after the command or use the `--opt=val` syntax instead of `--opt val`, so that Sqitch doesn't think `val` is the command. Even in that case, it will search the rest of the arguments to find a valid command. However, to minimize this challenge, the documentation now suggests and demonstrates putting all options after the command, like so: `sqitch [command] [options]`. - Simplified and clarified the distinction between core and command options by removing all options from the core except those that affect output and runtime context. The core options are: * -C --chdir --cd