use
5.010;
our
$VERSION
=
'v1.5.1'
;
has
uri
=> (
is
=>
'ro'
,
isa
=> URIDB,
lazy
=> 1,
default
=>
sub
{
my
$self
=
shift
;
my
$uri
=
$self
->SUPER::uri;
$uri
->host(
$ENV
{MYSQL_HOST})
if
!
$uri
->host &&
$ENV
{MYSQL_HOST};
$uri
->port(
$ENV
{MYSQL_TCP_PORT})
if
!
$uri
->_port &&
$ENV
{MYSQL_TCP_PORT};
return
$uri
;
},
);
has
registry_uri
=> (
is
=>
'ro'
,
isa
=> URIDB,
lazy
=> 1,
default
=>
sub
{
my
$self
=
shift
;
my
$uri
=
$self
->uri->clone;
$uri
->dbname(
$self
->registry);
return
$uri
;
},
);
sub
registry_destination {
my
$uri
=
shift
->registry_uri;
if
(
$uri
->password) {
$uri
=
$uri
->clone;
$uri
->password(
undef
);
}
return
$uri
->as_string;
}
has
_mycnf
=> (
is
=>
'rw'
,
isa
=> HashRef,
default
=>
sub
{
eval
'require MySQL::Config; 1'
or
return
{};
return
scalar
MySQL::Config::parse_defaults(
'my'
, [
qw(client mysql)
]);
},
);
sub
_def_user {
$_
[0]->_mycnf->{user} ||
$_
[0]->sqitch->sysuser }
sub
_def_pass {
$ENV
{MYSQL_PWD} ||
shift
->_mycnf->{password} }
sub
_dsn {
(
my
$dsn
=
shift
->registry_uri->dbi_dsn) =~ s/\Adbi:mysql/dbi:MariaDB/;
return
$dsn
;
}
has
dbh
=> (
is
=>
'rw'
,
isa
=> DBH,
lazy
=> 1,
default
=>
sub
{
my
$self
=
shift
;
$self
->use_driver;
my
$dbh
= DBI->
connect
(
$self
->_dsn,
$self
->username,
$self
->password, {
PrintError
=> 0,
RaiseError
=> 0,
AutoCommit
=> 1,
HandleError
=>
$self
->error_handler,
Callbacks
=> {
connected
=>
sub
{
my
$dbh
=
shift
;
$dbh
->
do
(
"SET SESSION $_"
) or
return
for
(
q{character_set_client = 'utf8'}
,
q{character_set_server = 'utf8'}
,
(
$dbh
->{mariadb_serverversion} || 0 < 50500 ? () : (
q{default_storage_engine = 'InnoDB'}
)),
q{time_zone = '+00:00'}
,
q{group_concat_max_len = 32768}
,
q{sql_mode = '}
.
join
(',',
qw(
ansi
strict_trans_tables
no_auto_value_on_zero
no_zero_date
no_zero_in_date
only_full_group_by
error_for_division_by_zero
)
) .
q{'}
,
);
return
;
},
},
});
my
(
$dbms
,
$vnum
,
$vstr
) =
$dbh
->{mariadb_serverinfo} =~ /mariadb/i
? (
'MariaDB'
, 50300,
'5.3'
)
: (
'MySQL'
, 50100,
'5.1.0'
);
hurl
mysql
=> __x(
'Sqitch requires {rdbms} {want_version} or higher; this is {have_version}'
,
rdbms
=>
$dbms
,
want_version
=>
$vstr
,
have_version
=>
$dbh
->selectcol_arrayref(
'SELECT version()'
)->[0],
)
unless
$dbh
->{mariadb_serverversion} >=
$vnum
;
return
$dbh
;
}
);
has
_ts_default
=> (
is
=>
'ro'
,
isa
=> Str,
lazy
=> 1,
default
=>
sub
{
return
'utc_timestamp(6)'
if
shift
->_fractional_seconds;
return
'utc_timestamp'
;
},
);
has
_mysql
=> (
is
=>
'ro'
,
isa
=> ArrayRef,
lazy
=> 1,
default
=>
sub
{
my
$self
=
shift
;
my
$uri
=
$self
->uri;
$self
->sqitch->
warn
(__x
'Database name missing in URI "{uri}"'
,
uri
=>
$uri
)
unless
$uri
->dbname;
my
@ret
= (
$self
->client );
for
my
$spec
(
[
user
=>
$self
->username ],
[
database
=>
$uri
->dbname ],
[
host
=>
$uri
->host ],
[
port
=>
$uri
->_port ],
) {
push
@ret
,
"--$spec->[0]"
=>
$spec
->[1]
if
$spec
->[1];
}
if
(
my
$pw
=
$self
->password) {
my
$cfgpwd
=
$self
->_mycnf->{password} ||
''
;
push
@ret
,
"--password=$pw"
if
$pw
ne
$cfgpwd
;
}
push
@ret
=> (
(App::Sqitch::ISWIN ? () :
'--skip-pager'
),
'--silent'
,
'--skip-column-names'
,
'--skip-line-numbers'
,
);
my
$vinfo
=
try
{
$self
->sqitch->probe(
$self
->client,
'--version'
) } ||
''
;
if
(
$vinfo
=~ /mariadb/i) {
my
(
$version
) =
$vinfo
=~ /(?:Ver|client)\s+(\S+)/;
my
(
$maj
,
undef
,
$pat
) =
split
/[.]/ =>
$version
;
push
@ret
=>
'--abort-source-on-error'
if
$maj
> 5 || (
$maj
== 5 &&
$pat
>= 66);
}
if
(
my
@p
=
$uri
->query_params) {
my
%option_for
= (
mysql_compression
=>
sub
{
$_
[0] ?
'--compress'
: () },
mysql_ssl
=>
sub
{
$_
[0] ?
'--ssl'
: () },
mysql_connect_timeout
=>
sub
{
'--connect_timeout'
,
$_
[0] },
mysql_init_command
=>
sub
{
'--init-command'
,
$_
[0] },
mysql_socket
=>
sub
{
'--socket'
,
$_
[0] },
mysql_ssl_client_key
=>
sub
{
'--ssl-key'
,
$_
[0] },
mysql_ssl_client_cert
=>
sub
{
'--ssl-cert'
,
$_
[0] },
mysql_ssl_ca_file
=>
sub
{
'--ssl-ca'
,
$_
[0] },
mysql_ssl_ca_path
=>
sub
{
'--ssl-capath'
,
$_
[0] },
mysql_ssl_cipher
=>
sub
{
'--ssl-cipher'
,
$_
[0] },
);
while
(
@p
) {
my
(
$k
,
$v
) = (
shift
@p
,
shift
@p
);
my
$code
=
$option_for
{
$k
} or
next
;
push
@ret
=>
$code
->(
$v
);
}
}
return
\
@ret
;
},
);
has
_fractional_seconds
=> (
is
=>
'ro'
,
isa
=> Bool,
lazy
=> 1,
default
=>
sub
{
my
$dbh
=
shift
->dbh;
return
$dbh
->{mariadb_serverinfo} =~ /mariadb/i
?
$dbh
->{mariadb_serverversion} >= 50305
:
$dbh
->{mariadb_serverversion} >= 50604;
},
);
sub
mysql { @{
shift
->_mysql } }
sub
key {
'mysql'
}
sub
name {
'MySQL'
}
sub
driver {
'DBD::MariaDB 1.0'
}
sub
default_client {
'mysql'
}
sub
_char2ts {
$_
[1]->set_time_zone(
'UTC'
)->iso8601;
}
sub
_ts2char_format {
return
q{date_format(%s, 'year:%%Y:month:%%m:day:%%d:hour:%%H:minute:%%i:second:%%S:time_zone:UTC')}
;
}
sub
_quote_idents {
shift
;
map
{
$_
eq
'change'
?
'"change"'
:
$_
}
@_
;
}
sub
_version_query {
'SELECT CAST(ROUND(MAX(version), 1) AS CHAR) FROM releases'
}
has
initialized
=> (
is
=>
'ro'
,
isa
=> Bool,
lazy
=> 1,
writer
=>
'_set_initialized'
,
default
=>
sub
{
my
$self
=
shift
;
my
$dbh
=
try
{
$self
->dbh }
catch
{
return
if
$DBI::err
&&
$DBI::err
== 1049;
die
$_
;
} or
return
0;
return
$dbh
->selectcol_arrayref(
q{
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = ?
AND table_name = ?
}
,
undef
,
$self
->registry,
'changes'
)->[0];
}
);
sub
_initialize {
my
$self
=
shift
;
hurl
engine
=> __x(
'Sqitch database {database} already initialized'
,
database
=>
$self
->registry,
)
if
$self
->initialized;
(
my
$db
=
$self
->registry) =~ s/
"/"
"/g;
$self
->_run(
'--execute'
=>
sprintf
(
'SET sql_mode = ansi; CREATE DATABASE IF NOT EXISTS "%s"'
,
$self
->registry
),
);
$self
->run_upgrade( file(__FILE__)->dir->file(
'mysql.sql'
) );
$self
->_set_initialized(1);
$self
->dbh->
do
(
'USE '
.
$self
->dbh->quote_identifier(
$self
->registry));
$self
->_register_release;
}
sub
begin_work {
my
$self
=
shift
;
my
$dbh
=
$self
->dbh;
$dbh
->
do
(
'LOCK TABLES '
.
join
', '
,
map
{
"$_ WRITE"
}
qw(releases changes dependencies events projects tags)
);
$dbh
->begin_work;
return
$self
;
}
sub
_lock_name {
'sqitch working on '
. (
shift
->uri->dbname //
''
)
}
sub
try_lock {
my
$self
=
shift
;
$self
->initialize
unless
$self
->initialized;
$self
->dbh->selectcol_arrayref(
q{SELECT get_lock(?, ?)}
,
undef
,
$self
->_lock_name, 0,
)->[0]
}
sub
wait_lock {
my
$self
=
shift
;
$self
->dbh->selectcol_arrayref(
q{SELECT get_lock(?, ?)}
,
undef
,
$self
->_lock_name,
$self
->lock_timeout,
)->[0]
}
sub
finish_work {
my
$self
=
shift
;
my
$dbh
=
$self
->dbh;
$dbh
->commit;
$dbh
->
do
(
'UNLOCK TABLES'
);
return
$self
;
}
sub
_no_table_error {
return
$DBI::state
&& (
$DBI::state
eq
'42S02'
||
(
$DBI::state
eq
'42000'
&&
$DBI::err
==
'1049'
)
)
}
sub
_no_column_error {
return
$DBI::state
&&
$DBI::state
eq
'42S22'
&&
$DBI::err
==
'1054'
;
}
sub
_unique_error {
return
$DBI::state
&&
$DBI::state
eq
'23000'
&&
$DBI::err
==
'1062'
;
}
sub
_regex_op {
'REGEXP'
}
sub
_limit_default {
'18446744073709551615'
}
sub
_listagg_format {
return
q{GROUP_CONCAT(%1$s ORDER BY %1$s SEPARATOR ' ')}
;
}
sub
_prepare_to_log {
my
(
$self
,
$table
,
$change
) =
@_
;
return
$self
if
$self
->_fractional_seconds;
my
$dbh
=
$self
->dbh;
my
$sth
=
$dbh
->prepare(
qq{
SELECT UNIX_TIMESTAMP(committed_at) >= UNIX_TIMESTAMP()
FROM $table
WHERE project = ?
ORDER BY committed_at DESC
LIMIT 1
}
);
while
(
$dbh
->selectcol_arrayref(
$sth
,
undef
,
$change
->project)->[0]) {
Time::HiRes::
sleep
(0.1);
}
return
$self
;
}
sub
_set_vars {
my
%vars
=
shift
->variables or
return
;
return
'SET '
.
join
(
', '
,
map
{
(
my
$k
=
$_
) =~ s/
"/"
"/g;
(
my
$v
=
$vars
{
$_
}) =~ s/
'/'
'/g;
qq{\@"$k" = '$v'}
;
}
sort
keys
%vars
) .
";\n"
;
}
sub
_source {
my
(
$self
,
$file
) =
@_
;
my
$set
=
$self
->_set_vars ||
''
;
return
(
'--execute'
=>
"${set}source $file"
);
}
sub
_run {
my
$self
=
shift
;
my
$sqitch
=
$self
->sqitch;
my
$pass
=
$self
->password or
return
$sqitch
->run(
$self
->mysql,
@_
);
local
$ENV
{MYSQL_PWD} =
$pass
;
return
$sqitch
->run(
$self
->mysql,
@_
);
}
sub
_capture {
my
$self
=
shift
;
my
$sqitch
=
$self
->sqitch;
my
$pass
=
$self
->password or
return
$sqitch
->capture(
$self
->mysql,
@_
);
local
$ENV
{MYSQL_PWD} =
$pass
;
return
$sqitch
->capture(
$self
->mysql,
@_
);
}
sub
_spool {
my
$self
=
shift
;
my
@fh
= (
shift
);
my
$sqitch
=
$self
->sqitch;
if
(
my
$set
=
$self
->_set_vars) {
open
my
$sfh
,
'<:utf8_strict'
, \
$set
;
unshift
@fh
,
$sfh
;
}
my
$pass
=
$self
->password or
return
$sqitch
->spool( \
@fh
,
$self
->mysql,
@_
);
local
$ENV
{MYSQL_PWD} =
$pass
;
return
$sqitch
->spool( \
@fh
,
$self
->mysql,
@_
);
}
sub
run_file {
my
$self
=
shift
;
$self
->_run(
$self
->_source(
@_
) );
}
sub
run_verify {
my
$self
=
shift
;
my
$meth
=
$self
->can(
$self
->sqitch->verbosity > 1 ?
'_run'
:
'_capture'
);
$self
->
$meth
(
$self
->_source(
@_
) );
}
sub
run_upgrade {
my
(
$self
,
$file
) =
@_
;
my
@cmd
=
$self
->mysql;
if
((
my
$idx
= firstidx {
$_
eq
'--database'
}
@cmd
) > 0) {
$cmd
[
$idx
+ 1] =
$self
->registry;
}
else
{
push
@cmd
=>
'--database'
,
$self
->registry;
}
return
$self
->sqitch->run(
@cmd
,
$self
->_source(
$self
->_prepare_registry_file(
$file
)),
);
}
sub
_prepare_registry_file {
my
(
$self
,
$file
) =
@_
;
my
$can_create_checkit
=
$self
->_can_create_immutable_function;
my
$has_frac
=
$self
->_fractional_seconds;
return
$file
if
$has_frac
&&
$can_create_checkit
;
my
$sql
=
$file
->slurp;
if
(!
$has_frac
) {
$sql
=~ s{DATETIME\(\d+\)}{DATETIME}g;
$sql
=~ s/--
if
$self
->dbh->{mariadb_serverversion} < 50500;
}
if
(!
$can_create_checkit
) {
$self
->sqitch->
warn
(__(
'Insufficient permissions to create the checkit() function; skipping.'
,
));
$sql
=~ s/--
}
my
$fh
= File::Temp->new;
print
$fh
$sql
;
close
$fh
;
return
$fh
;
}
sub
_can_create_immutable_function {
shift
->dbh->selectcol_arrayref(
q{
SELECT @@log_bin = 0
OR @@log_bin_trust_function_creators = 1
OR (
SELECT md5(super_priv) = md5('Y') FROM mysql.user
WHERE CONCAT(user, '@', host) = current_user
)
}
)->[0]
}
sub
run_handle {
my
(
$self
,
$fh
) =
@_
;
$self
->_spool(
$fh
);
}
sub
_cid {
my
(
$self
,
$ord
,
$offset
,
$project
) =
@_
;
my
$offexpr
=
$offset
?
" OFFSET $offset"
:
''
;
return
try
{
return
$self
->dbh->selectcol_arrayref(
qq{
SELECT change_id
FROM changes
WHERE project = ?
ORDER BY committed_at $ord
LIMIT 1$offexpr
}
,
undef
,
$project
||
$self
->plan->project)->[0];
}
catch
{
return
if
$DBI::err
&& (
$DBI::err
== 1049 ||
$DBI::err
== 1146);
die
$_
;
};
}
1;
1;