Revision history for DBIx::Class
- DBIx::Class::InflateColumn::File entered deprecated state
+ - DBIx::Class::Optional::Dependencies left experimental state
+ - Add req_group_list to Opt::Deps (RT#55211)
+ - Cascading delete/update are now wrapped in a transaction
+ for atomicity
- Fix regression where SQL files with comments were not
handled properly by ::Schema::Versioned.
- Fix regression on not properly throwing when $obj->relationship
require DBIx::Class::Optional::Dependencies;
$reqs->{test_requires} = {
%{$reqs->{test_requires}},
- %{DBIx::Class::Optional::Dependencies->_all_optional_requirements},
+ map { %$_ } (values %{DBIx::Class::Optional::Dependencies->req_group_list}),
};
}
=item .. sort my results based on fields I've aliased using C<as>?
-You don't. You'll need to supply the same functions/expressions to
-C<order_by>, as you did to C<select>.
-
-To get "fieldname AS alias" in your SQL, you'll need to supply a
-literal chunk of SQL in your C<select> attribute, such as:
-
- ->search({}, { select => [ \'now() AS currenttime'] })
-
-Then you can use the alias in your C<order_by> attribute.
+You didn't alias anything, since L<as|DBIx::Class::ResultSet/as>
+B<has nothing to do> with the produced SQL. See
+L<DBIx::Class::ResultSet/select> for details.
=item .. group the results of my search?
=item .. group my results based on fields I've aliased using C<as>?
-You don't. You'll need to supply the same functions/expressions to
-C<group_by>, as you did to C<select>.
-
-To get "fieldname AS alias" in your SQL, you'll need to supply a
-literal chunk of SQL in your C<select> attribute, such as:
-
- ->search({}, { select => [ \'now() AS currenttime'] })
-
-Then you can use the alias in your C<group_by> attribute.
+You don't. See the explanation on ordering by an alias above.
=item .. filter the results of my search?
test_notabs => {
req => {
- #'Test::NoTabs' => '0.9',
+ 'Test::NoTabs' => '0.9',
},
},
test_eol => {
req => {
- #'Test::EOL' => '0.6',
+ 'Test::EOL' => '0.6',
},
},
};
-sub _all_optional_requirements {
- return { map { %{ $reqs->{$_}{req} || {} } } (keys %$reqs) };
-}
-
sub req_list_for {
my ($class, $group) = @_;
}
}
-# This is to be called by the author onbly (automatically in Makefile.PL)
+sub req_group_list {
+ return { map { $_ => { %{ $reqs->{$_}{req} || {} } } } (keys %$reqs) };
+}
+
+# This is to be called by the author only (automatically in Makefile.PL)
sub _gen_pod {
my $class = shift;
my $modfn = __PACKAGE__ . '.pm';
require DBIx::Class;
my $distver = DBIx::Class->VERSION;
+ my $sqltver = $class->req_list_for ('deploy')->{'SQL::Translator'}
+ or die "Hrmm? No sqlt dep?";
my @chunks = (
<<"EOC",
EOC
'=head1 NAME',
"$class - Optional module dependency specifications (for module authors)",
- '=head1 SYNOPSIS (EXPERIMENTAL)',
+ '=head1 SYNOPSIS',
<<EOS,
-B<THE USAGE SHOWN HERE IS EXPERIMENTAL>
-
Somewhere in your build-file (e.g. L<Module::Install>'s Makefile.PL):
...
push @chunks, (
'=head1 METHODS',
+ '=head2 req_group_list',
+ '=over',
+ '=item Arguments: $none',
+ '=item Returns: \%list_of_requirement_groups',
+ '=back',
+ <<EOD,
+This method should be used by DBIx::Class packagers, to get a hashref of all
+dependencies keyed by dependency group. Each key (group name) can be supplied
+to one of the group-specific methods below.
+EOD
+
'=head2 req_list_for',
'=over',
'=item Arguments: $group_name',
<<EOD,
This method should be used by DBIx::Class extension authors, to determine the
version of modules a specific feature requires in the B<current> version of
-DBIx::Class. See the L<SYNOPSIS|/SYNOPSIS (EXPERIMENTAL)> for a real-world
+DBIx::Class. See the L</SYNOPSIS> for a real-world
example.
EOD
indicate to the user that he needs to install specific modules before he will
be able to use a specific feature.
-For example if the requirements for C<replicated> are not available, the
-returned string would look like:
+For example if some of the requirements for C<deploy> are not available,
+the returned string could look like:
- Moose >= 0.98, MooseX::Types >= 0.21, namespace::clean (see $class for details)
+ SQL::Translator >= $sqltver (see $class for details)
The author is expected to prepend the necessary text to this message before
returning the actual error seen by the user.
# be handling this anyway. Assuming we have joins we probably actually
# *could* do them, but I'd rather not.
- my $ret = $self->next::method(@rest);
-
my $source = $self->result_source;
my %rels = map { $_ => $source->relationship_info($_) } $source->relationships;
my @cascade = grep { $rels{$_}{attrs}{cascade_delete} } keys %rels;
- foreach my $rel (@cascade) {
- $self->search_related($rel)->delete_all;
+
+ if (@cascade) {
+ my $guard = $source->schema->txn_scope_guard;
+
+ my $ret = $self->next::method(@rest);
+
+ foreach my $rel (@cascade) {
+ $self->search_related($rel)->delete_all;
+ }
+
+ $guard->commit;
+ return $ret;
}
- return $ret;
+
+ $self->next::method(@rest);
}
sub update {
return $self->next::method(@rest) unless ref $self;
# Because update cascades on a class *really* don't make sense!
- my $ret = $self->next::method(@rest);
-
my $source = $self->result_source;
my %rels = map { $_ => $source->relationship_info($_) } $source->relationships;
my @cascade = grep { $rels{$_}{attrs}{cascade_update} } keys %rels;
- foreach my $rel (@cascade) {
- next if (
- $rels{$rel}{attrs}{accessor}
- &&
- $rels{$rel}{attrs}{accessor} eq 'single'
- &&
- !exists($self->{_relationship_data}{$rel})
- );
- $_->update for grep defined, $self->$rel;
+
+ if (@cascade) {
+ my $guard = $source->schema->txn_scope_guard;
+
+ my $ret = $self->next::method(@rest);
+
+ foreach my $rel (@cascade) {
+ next if (
+ $rels{$rel}{attrs}{accessor}
+ &&
+ $rels{$rel}{attrs}{accessor} eq 'single'
+ &&
+ !exists($self->{_relationship_data}{$rel})
+ );
+ $_->update for grep defined, $self->$rel;
+ }
+
+ $guard->commit;
+ return $ret;
}
- return $ret;
+
+ $self->next::method(@rest);
}
1;
select => [
'name',
{ count => 'employeeid' },
- { sum => 'salary' }
+ { max => { length => 'name' }, -as => 'longest_name' }
]
});
-When you use function/stored procedure names and do not supply an C<as>
-attribute, the column names returned are storage-dependent. E.g. MySQL would
-return a column named C<count(employeeid)> in the above example.
+ # Equivalent SQL
+ SELECT name, COUNT( employeeid ), MAX( LENGTH( name ) ) AS longest_name FROM employee
-B<NOTE:> You will almost always need a corresponding 'as' entry when you use
-'select'.
+B<NOTE:> You will almost always need a corresponding L</as> attribute when you
+use L</select>, to instruct DBIx::Class how to store the result of the column.
+Also note that the L</as> attribute has nothing to do with the SQL-side 'AS'
+identifier aliasing. You can however alias a function, so you can use it in
+e.g. an C<ORDER BY> clause. This is done via the C<-as> B<select function
+attribute> supplied as shown in the example above.
=head2 +select
=over 4
Indicates additional columns to be selected from storage. Works the same as
-L</select> but adds columns to the selection.
+L</select> but adds columns to the default selection, instead of specifying
+an explicit list.
=back
=back
-Indicates column names for object inflation. That is, C<as>
-indicates the name that the column can be accessed as via the
-C<get_column> method (or via the object accessor, B<if one already
-exists>). It has nothing to do with the SQL code C<SELECT foo AS bar>.
-
-The C<as> attribute is used in conjunction with C<select>,
-usually when C<select> contains one or more function or stored
-procedure names:
+Indicates column names for object inflation. That is L</as> indicates the
+slot name in which the column value will be stored within the
+L<Row|DBIx::Class::Row> object. The value will then be accessible via this
+identifier by the C<get_column> method (or via the object accessor B<if one
+with the same name already exists>) as shown below. The L</as> attribute has
+B<nothing to do> with the SQL-side C<AS>. See L</select> for details.
$rs = $schema->resultset('Employee')->search(undef, {
select => [
'name',
- { count => 'employeeid' }
+ { count => 'employeeid' },
+ { max => { length => 'name' }, -as => 'longest_name' }
],
- as => ['name', 'employee_count'],
+ as => [qw/
+ name
+ employee_count
+ max_name_length
+ /],
});
- my $employee = $rs->first(); # get the first Employee
-
If the object against which the search is performed already has an accessor
matching a column name specified in C<as>, the value can be retrieved using
the accessor as normal:
You can create your own accessors if required - see
L<DBIx::Class::Manual::Cookbook> for details.
-Please note: This will NOT insert an C<AS employee_count> into the SQL
-statement produced, it is used for internal access only. Thus
-attempting to use the accessor in an C<order_by> clause or similar
-will fail miserably.
-
-To get around this limitation, you can supply literal SQL to your
-C<select> attribute that contains the C<AS alias> text, e.g.
-
- select => [\'myfield AS alias']
-
=head2 join
=over 4
use lib 't/lib';
use DBICTest;
-my @MODULES = (
- 'Test::NoTabs 0.9',
-);
-
-plan skip_all => 'Does not work with done_testing, temp disabled';
-
# Don't run tests for installs
unless ( DBICTest::AuthorCheck->is_author || $ENV{AUTOMATED_TESTING} || $ENV{RELEASE_TESTING} ) {
plan( skip_all => "Author tests not required for installation" );
}
-# Load the testing modules
-foreach my $MODULE ( @MODULES ) {
- eval "use $MODULE";
- if ( $@ ) {
- $ENV{RELEASE_TESTING}
- ? die( "Failed to load required release-testing module $MODULE" )
- : plan( skip_all => "$MODULE not available for testing" );
- }
+
+require DBIx::Class;
+unless ( DBIx::Class::Optional::Dependencies->req_ok_for ('test_notabs') ) {
+ my $missing = DBIx::Class::Optional::Dependencies->req_missing_for ('test_notabs');
+ $ENV{RELEASE_TESTING} || DBICTest::AuthorCheck->is_author
+ ? die ("Failed to load release-testing module requirements: $missing")
+ : plan skip_all => "Test needs: $missing"
}
-all_perl_files_ok(qw/t lib script maint/);
+Test::NoTabs::all_perl_files_ok(qw/t lib script maint/);
-done_testing;
+# FIXME - need to fix Test::NoTabs
+#done_testing;
use lib 't/lib';
use DBICTest;
-my @MODULES = (
- 'Test::EOL 0.6',
-);
-
-plan skip_all => 'Does not work with done_testing, temp disabled';
-
# Don't run tests for installs
unless ( DBICTest::AuthorCheck->is_author || $ENV{AUTOMATED_TESTING} || $ENV{RELEASE_TESTING} ) {
plan( skip_all => "Author tests not required for installation" );
}
-# Load the testing modules
-foreach my $MODULE ( @MODULES ) {
- eval "use $MODULE";
- if ( $@ ) {
- $ENV{RELEASE_TESTING}
- ? die( "Failed to load required release-testing module $MODULE" )
- : plan( skip_all => "$MODULE not available for testing" );
- }
+
+plan skip_all => 'Test::EOL very broken';
+
+require DBIx::Class;
+unless ( DBIx::Class::Optional::Dependencies->req_ok_for ('test_eol') ) {
+ my $missing = DBIx::Class::Optional::Dependencies->req_missing_for ('test_eol');
+ $ENV{RELEASE_TESTING} || DBICTest::AuthorCheck->is_author
+ ? die ("Failed to load release-testing module requirements: $missing")
+ : plan skip_all => "Test needs: $missing"
}
TODO: {
local $TODO = 'Do not fix those yet - we have way too many branches out there, merging will be hell';
- all_perl_files_ok({ trailing_whitespace => 1}, qw/t lib script maint/);
+ Test::EOL::all_perl_files_ok({ trailing_whitespace => 1}, qw/t lib script maint/);
}
-done_testing;
+# FIXME - need to fix Test::EOL
+#done_testing;
no warnings 'redefine';
no strict 'refs';
- # die in rollback, but maintain sanity for further tests ...
+ # die in rollback
local *{"DBIx::Class::Storage::DBI::SQLite::txn_rollback"} = sub{
my $storage = shift;
- $storage->{transaction_depth}--;
die 'FAILED';
};
$schema->storage->_dbh->rollback;
}
+# reset schema object (the txn_rollback meddling screws it up)
+$schema = DBICTest->init_schema();
+
# Test nested failed txn_do()
{
is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0');
$employee->group_id(1);
$employee->update;
ok(
- check_rs($employees->search_rs({group_id=>1})) && check_rs($employees->search_rs({group_id=>4})),
- "overloaded update 3"
+ check_rs($employees->search_rs({group_id=>1})) && check_rs($employees->search_rs({group_id=>4})),
+ "overloaded update 3"
);
$employee = $employees->search({group_id=>4})->first;
$employee->update({group_id=>2});
ok(
- check_rs($employees->search_rs({group_id=>2})) && check_rs($employees->search_rs({group_id=>4})),
- "overloaded update 4"
+ check_rs($employees->search_rs({group_id=>2})) && check_rs($employees->search_rs({group_id=>4})),
+ "overloaded update 4"
);
$employee = $employees->search({group_id=>4})->first;
$employee->group_id(1);
$employee->position(3);
$employee->update;
ok(
- check_rs($employees->search_rs({group_id=>1})) && check_rs($employees->search_rs({group_id=>4})),
- "overloaded update 5"
+ check_rs($employees->search_rs({group_id=>1})) && check_rs($employees->search_rs({group_id=>4})),
+ "overloaded update 5"
);
$employee = $employees->search({group_id=>4})->first;
$employee->group_id(2);
$employee->position(undef);
$employee->update;
ok(
- check_rs($employees->search_rs({group_id=>2})) && check_rs($employees->search_rs({group_id=>4})),
- "overloaded update 6"
+ check_rs($employees->search_rs({group_id=>2})) && check_rs($employees->search_rs({group_id=>4})),
+ "overloaded update 6"
);
$employee = $employees->search({group_id=>4})->first;
$employee->update({group_id=>1,position=>undef});
ok(
- check_rs($employees->search_rs({group_id=>1})) && check_rs($employees->search_rs({group_id=>4})),
- "overloaded update 7"
+ check_rs($employees->search_rs({group_id=>1})) && check_rs($employees->search_rs({group_id=>4})),
+ "overloaded update 7"
);
# multicol tests begin here
$employee = $employees->search({group_id_2=>4, group_id_3=>1})->first;
$employee->group_id_2(1);
$employee->update;
-ok(
+ok(
check_rs($employees->search_rs({group_id_2=>4, group_id_3=>1}))
&& check_rs($employees->search_rs({group_id_2=>1, group_id_3=>1})),
"overloaded multicol update 1"
__PACKAGE__->set_primary_key('employee_id');
__PACKAGE__->position_column('position');
-#__PACKAGE__->add_unique_constraint(position_group => [ qw/position group_id/ ]);
-
-__PACKAGE__->mk_classdata('field_name_for', {
- employee_id => 'primary key',
- position => 'list position',
- group_id => 'collection column',
- name => 'employee name',
-});
+# Do not add unique constraints here - different groups are used throughout
+# the ordered tests
1;