use
Env
qw< ONLINEDDL_TEST_DEBUG ONLINEDDL_NO_ACTIVITY_TEST CDTEST_MASS_POPULATE CDTEST_DSN CDTEST_DBUSER CDTEST_DBPASS >
;
our
@EXPORT
=
qw< onlineddl_test >
;
my
$FILE
= file(__FILE__);
my
$root
=
$FILE
->dir->parent->parent->parent;
my
$db_file
=
$root
->file(
't'
,
$FILE
->basename.
'.db'
);
unless
(
$CDTEST_DSN
) {
$CDTEST_DSN
=
"dbi:SQLite:dbname=$db_file"
;
$CDTEST_DBUSER
=
''
;
$CDTEST_DBPASS
=
''
;
unlink
$db_file
if
-e
$db_file
;
}
my
$CHUNK_SIZE
=
$CDTEST_MASS_POPULATE
? 5000 : 3;
my
$dbms_name
= CDTest->dbms_name;
END {
unlink
$db_file
if
-e
$db_file
;
};
sub
import
{
my
$class
=
shift
;
my
$target
=
caller
;
$_
->
import
::into(
$target
)
for
qw<
Test2::Bundle::More
Test2::Tools::Compare
Test2::Tools::Exception
Test2::Tools::Explain
DBI
DBIx::BatchChunker
DBIx::OnlineDDL
CDTest
>
;
Env->
import
::into(
$target
,
qw<
ONLINEDDL_TEST_DEBUG ONLINEDDL_NO_ACTIVITY_TEST CDTEST_MASS_POPULATE CDTEST_DSN CDTEST_DBUSER CDTEST_DBPASS
>
);
$class
->export_to_level(1,
@EXPORT
);
}
sub
onlineddl_test ($$&) {
my
(
$test_name
,
$source_name
,
$test_code
) =
@_
;
subtest_streamed(
"$source_name: $test_name"
,
sub
{
my
$cd_schema
;
try_ok {
$cd_schema
= CDTest->init_schema(
$CDTEST_DSN
&&
$CDTEST_DSN
=~ /^dbi:mysql:/ ? (
on_connect_call
=>
'set_strict_mode'
) : ()
);
}
'Tables created'
;
die
'Schema initialization failed!'
if
$@;
my
$rsrc
=
$cd_schema
->source(
$source_name
);
my
$rs
=
$cd_schema
->resultset(
$source_name
);
my
$old_table_name
=
$rsrc
->from;
my
$is_drop_pk
=
$test_name
eq
'Drop PK'
;
my
@alt_columns
;
my
%columns_info
= %{
$rsrc
->columns_info };
my
%uniques
=
$rsrc
->unique_constraints;
foreach
my
$constraint_name
(
sort
keys
%uniques
) {
next
if
join
(
','
,
sort
$rsrc
->primary_columns) eq
join
(
','
,
sort
@{
$uniques
{
$constraint_name
}});
@alt_columns
= @{
$uniques
{
$constraint_name
}};
last
;
}
$rsrc
->_primaries(\
@alt_columns
)
if
$is_drop_pk
&&
@alt_columns
;
my
$row_count
=
$rs
->count;
my
@select_columns
;
foreach
my
$column_name
(
sort
$rsrc
->columns) {
next
if
grep
{
$_
eq
$column_name
}
$rsrc
->primary_columns,
@alt_columns
;
my
$column_info
=
$columns_info
{
$column_name
};
next
if
$column_info
->{is_auto_increment};
next
if
$column_info
->{is_nullable};
push
@select_columns
,
$column_name
;
}
my
@id_columns
= (
$is_drop_pk
? () : (
$rsrc
->primary_columns),
@alt_columns
);
my
$iu_rs
=
$rs
->search(
undef
, {
columns
=> [
@id_columns
,
@select_columns
],
order_by
=> {
-desc
=> [
@id_columns
] },
rows
=> 1,
});
my
$del_rs
=
$rs
->search(
undef
, {
columns
=> [
@id_columns
,
@select_columns
],
order_by
=> {
-asc
=> [
@id_columns
] },
rows
=> 1,
});
my
$dc_count
= 0;
my
$activity_sim_sub
=
sub
{
my
(
$oddl
,
$dbh
) =
@_
;
my
$row
=
$iu_rs
->first;
$iu_rs
->
reset
;
return
unless
$row
;
my
$method
= (
caller
(2))[3];
$method
= (
caller
(3))[3]
if
$method
eq
'DBIx::OnlineDDL::Helper::Base::dbh'
;
foreach
my
$i
(0, 1) {
my
%new_row_vals
;
foreach
my
$column_name
(
@alt_columns
,
@select_columns
) {
my
$column_info
=
$columns_info
{
$column_name
};
$new_row_vals
{
$column_name
} =
$column_info
->{is_foreign_key} ?
$row
->get_column(
$column_name
) :
autofill_column(
$column_info
)
;
}
next
unless
%new_row_vals
;
$rs
->create(\
%new_row_vals
);
$row_count
++;
note
"During $method: Inserted "
.
join
(
', '
,
map
{
"$_ = "
.
$new_row_vals
{
$_
} }
grep
{
$new_row_vals
{
$_
} }
@id_columns
)
if
$ONLINEDDL_TEST_DEBUG
;
}
my
$id_str
=
join
(
', '
,
map
{
"$_ = "
.(
$row
->get_column(
$_
) //
'NULL'
) }
sort
@id_columns
);
foreach
my
$column_name
(
@select_columns
) {
my
$column_info
=
$columns_info
{
$column_name
};
next
if
$column_info
->{is_foreign_key};
$row
->set_column(
$column_name
=> autofill_column(
$column_info
) );
$row
->update;
note
"During $method: Updated $id_str"
if
$ONLINEDDL_TEST_DEBUG
;
last
;
}
unless
(
$oddl
->coderef_hooks &&
$oddl
->coderef_hooks->{before_swap}) {
$row
=
$del_rs
->first;
$del_rs
->
reset
;
return
$dbh
unless
$row
;
$id_str
=
join
(
', '
,
map
{
"$_ = "
.(
$row
->get_column(
$_
) //
'NULL'
) }
sort
@id_columns
);
$row
->
delete
;
$row_count
--;
note
"During $method: Deleted $id_str"
if
$ONLINEDDL_TEST_DEBUG
;
}
my
$vars
=
$oddl
->_vars;
my
$todo
;
$todo
= todo
'SQLite trigger weirdness'
if
$dbms_name
eq
'SQLite'
&&
$test_name
eq
'Add column + title change'
&&
$vars
->{new_table_copied}
;
my
(
$new_row_count
) =
$dbh
->selectrow_array(
"SELECT COUNT(*) FROM $old_table_name"
);
cmp_ok(
$new_row_count
,
'=='
,
$row_count
,
"Row counts from '$old_table_name' are as expected ($method)"
);
if
(
$vars
->{new_table_copied} && !
$vars
->{new_table_swapped}) {
my
$table_name
=
$oddl
->new_table_name;
my
(
$new_row_count
) =
$dbh
->selectrow_array(
"SELECT COUNT(*) FROM $table_name"
);
cmp_ok(
$new_row_count
,
'=='
,
$row_count
,
"Row counts from '$table_name' are as expected ($method)"
);
}
$dc_count
++;
unless
(
$method
=~ /(?:BUILD|post_connection_stmts|_build_helper|current_catalog_schema)$/ ||
$dc_count
% 3) {
if
(
$dbms_name
eq
'MySQL'
) {
eval
{
$dbh
->
do
(
'KILL CONNECTION CONNECTION_ID()'
) };
}
else
{
$dbh
->disconnect;
}
}
};
no
warnings
'redefine'
;
my
$orig_dbh_sub
= \
&DBIx::OnlineDDL::dbh
;
local
*DBIx::OnlineDDL::dbh
=
$ONLINEDDL_NO_ACTIVITY_TEST
?
$orig_dbh_sub
:
sub
{
my
$dbh
=
$orig_dbh_sub
->(
@_
);
return
$dbh
unless
$dbh
;
my
$oddl
=
shift
;
$activity_sim_sub
->(
$oddl
,
$dbh
);
return
$dbh
;
};
eval
{
$test_code
->(
$cd_schema
) };
fail
'Test died'
, $@
if
$@;
unless
($@) {
my
$todo
;
$todo
= todo
'SQLite trigger weirdness'
if
$dbms_name
eq
'SQLite'
&&
$test_name
eq
'Add column + title change'
;
my
$new_row_count
=
$rs
->count;
cmp_ok(
$new_row_count
,
'=='
,
$row_count
,
'Final row counts are as expected'
);
}
try_ok { CDTest->clean_schema(
$cd_schema
) }
'Tables dropped'
;
});
}
sub
autofill_column {
my
(
$column_info
) =
@_
;
my
$data_type
=
$column_info
->{data_type};
my
$size
=
$column_info
->{size} || 100;
return
$data_type
=~ /^(?:var)?char$/ ?
substr
( CDTest->_random_words, 0,
$size
) :
$data_type
=~ /^
int
(eger)?$/ ?
int
(
rand
(2_000_000))+1000 :
die
"Not sure how to auto-fill for data type '$data_type'!"
;
}