use inc::Module::Install 0.67;
use strict;
use warnings;
+use POSIX ();
use 5.006001; # delete this line if you want to send patches for earlier.
requires 'Path::Class' => 0;
requires 'List::Util' => 1.19;
requires 'Sub::Name' => 0.04;
+requires 'namespace::clean' => 0.09;
# Perl 5.8.0 doesn't have utf8::is_utf8()
requires 'Encode' => 0 if ($] <= 5.008000);
-test_requires 'DBD::SQLite' => 1.14;
+configure_requires 'DBD::SQLite' => 1.14;
+
test_requires 'Test::Builder' => 0.33;
test_requires 'Test::Warn' => 0.11;
test_requires 'Test::Exception' => 0;
exit 0;
}
else {
- wait();
+ eval {
+ local $SIG{ALRM} = sub { die "timeout\n" };
+ alarm 5;
+ wait();
+ alarm 0;
+ };
my $sig = $? & 127;
- if ($sig == 11) {
+ if ($@ || $sig == POSIX::SIGSEGV()) {
warn (<<EOE);
############################### WARNING #################################
}
-WriteAll;
+WriteAll();
if ($Module::Install::AUTHOR) {
starts_when => { data_type => 'datetime' }
);
+NOTE: You B<must> load C<InflateColumn::DateTime> B<before> C<Core>. See
+L<DBIx::Class::Manual::Component> for details.
+
Then you can treat the specified column as a L<DateTime> object.
print "This event starts the month of ".
are in any way unsure about the use of the attributes above (C< join
>, C< select >, C< as > and C< group_by >).
+=head2 Subqueries
+
+You can write subqueries relatively easily in DBIC.
+
+ my $inside_rs = $schema->resultset('Artist')->search({
+ name => [ 'Billy Joel', 'Brittany Spears' ],
+ });
+
+ my $rs = $schema->resultset('CD')->search({
+ artist_id => { 'IN' => $inside_rs->get_column('id')->as_query },
+ });
+
+The usual operators ( =, !=, IN, NOT IN, etc) are supported.
+
+B<NOTE>: You have to explicitly use '=' when doing an equality comparison.
+The following will B<not> work:
+
+ my $rs = $schema->resultset('CD')->search({
+ artist_id => $inside_rs->get_column('id')->as_query,
+ });
+
+=head3 Correlated subqueries
+
+ my $cdrs = $schema->resultset('CD');
+ my $rs = $cdrs->search({
+ year => {
+ '=' => $cdrs->search(
+ { artistid => { '=' => \'me.artistid' } },
+ { alias => 'inner' }
+ )->get_column('year')->max_rs->as_query,
+ },
+ });
+
+That creates the following SQL:
+
+ SELECT me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track
+ FROM cd me
+ WHERE year = (
+ SELECT MAX(inner.year)
+ FROM cd inner
+ WHERE artistid = me.artistid
+ )
+
+=head2 Where subqueries will work
+
+Currently, subqueries will B<only> work in the where-clause of a search. In
+other words, in the first hashref of a search() method. Work is being done
+to make them work as part of the second hashref (from, select, +select, etc).
+
=head2 Predefined searches
You can write your own L<DBIx::Class::ResultSet> class by inheriting from it
my $hash_ref = $rs->find(1);
Wasn't that easy?
-
+
=head2 Get raw data for blindingly fast results
If the L<HashRefInflator|DBIx::Class::ResultClass::HashRefInflator> solution
statement and dig down to see if certain parameters cause aberrant behavior.
You might want to check out L<DBIx::Class::QueryLog> as well.
+=head1 STARTUP SPEED
+
+L<DBIx::Class|DBIx::Class> programs can have a significant startup delay
+as the ORM loads all the relevant classes. This section examines
+techniques for reducing the startup delay.
+
+These tips are are listed in order of decreasing effectiveness - so the
+first tip, if applicable, should have the greatest effect on your
+application.
+
+=head2 Statically Define Your Schema
+
+If you are using
+L<DBIx::Class::Schema::Loader|DBIx::Class::Schema::Loader> to build the
+classes dynamically based on the database schema then there will be a
+significant startup delay.
+
+For production use a statically defined schema (which can be generated
+using L<DBIx::Class::Schema::Loader|DBIx::Class::Schema::Loader> to dump
+the database schema once - see
+L<make_schema_at|DBIx::Class::Schema::Loader/make_schema_at> and
+L<dump_directory|DBIx::Class::Schema::Loader/dump_directory> for more
+details on creating static schemas from a database).
+
+=head2 Move Common Startup into a Base Class
+
+Typically L<DBIx::Class> result classes start off with
+
+ use base qw/DBIx::Class/;
+ __PACKAGE__->load_components(qw/InflateColumn::DateTime Core/);
+
+If this preamble is moved into a common base class:-
+
+ package MyDBICbase;
+
+ use base qw/DBIx::Class/;
+ __PACKAGE__->load_components(qw/InflateColumn::DateTime Core/);
+ 1;
+
+and each result class then uses this as a base:-
+
+ use base qw/MyDBICbase/;
+
+then the load_components is only performed once, which can result in a
+considerable startup speedup for schemas with many classes.
+
+=head2 Explicitly List Schema Result Classes
+
+The schema class will normally contain
+
+ __PACKAGE__->load_classes();
+
+to load the result classes. This will use L<Module::Find|Module::Find>
+to find and load the appropriate modules. Explicitly defining the
+classes you wish to load will remove the overhead of
+L<Module::Find|Module::Find> and the related directory operations:-
+
+ __PACKAGE__->load_classes(qw/ CD Artist Track /);
+
+If you are instead using the L<load_namespaces|DBIx::Class::Schema/load_namespaces>
+syntax to load the appropriate classes there is not a direct alternative
+avoiding L<Module::Find|Module::Find>.
=cut
be alarmed if the error from incorrect connection details happens a
lot later.
+=item .. use DBIx::Class across multiple databases?
+
+If your database server allows you to run querys across multiple
+databases at once, then so can DBIx::Class. All you need to do is make
+sure you write the database name as part of the
+L<DBIx::Class::ResultSource/table> call. Eg:
+
+ __PACKAGE__->table('mydb.mytablename');
+
+And load all the Result classes for both / all databases using one
+L<DBIx::Class::Schema/load_namespaces> call.
+
+=item .. use DBIx::Class across PostgreSQL/DB2/Oracle schemas?
+
+Add the name of the schema to the L<DBIx::Class::ResultSource/table>
+as part of the name, and make sure you give the one user you are going
+to connect with rights to read/write all the schemas/tables as
+necessary.
=back
$resultset->set_primary_key(@column);
+=item How do I make my program start faster?
+
+Look at the tips in L<DBIx::Class::Manual::Cookbook/"STARTUP SPEED">
+
+=item How do I reduce the overhead of database queries?
+
+You can reduce the overhead of object creation within L<DBIx::Class>
+using the tips in L<DBIx::Class::Manual::Cookbook/"Skip row object creation for faster results">
+and L<DBIx::Class::Manual::Cookbook/"Get raw data for blindingly fast results">
+
=back
=head2 Notes for CDBI users
(ref $cond ? ref $cond : 'plain scalar')
) unless ref $cond eq 'HASH';
if (defined $f_obj) {
- my $f_class = $self->result_source->schema->class($rel_obj->{class});
+ my $f_class = $rel_obj->{class};
$self->throw_exception( "Object $f_obj isn't a ".$f_class )
unless Scalar::Util::blessed($f_obj) and $f_obj->isa($f_class);
}
=head1 NAME
-DBIx::Class::ResultSet - Responsible for fetching and creating resultset.
+DBIx::Class::ResultSet - Represents a query used for fetching a set of results.
=head1 SYNOPSIS
- my $rs = $schema->resultset('User')->search({ registered => 1 });
- my @rows = $schema->resultset('CD')->search({ year => 2005 })->all();
+ my $users_rs = $schema->resultset('User');
+ my $registered_users_rs = $schema->resultset('User')->search({ registered => 1 });
+ my @cds_in_2005 = $schema->resultset('CD')->search({ year => 2005 })->all();
=head1 DESCRIPTION
-The resultset is also known as an iterator. It is responsible for handling
-queries that may return an arbitrary number of rows, e.g. via L</search>
-or a C<has_many> relationship.
+A ResultSet is an object which stores a set of conditions representing
+a query. It is the backbone of DBIx::Class (i.e. the really
+important/useful bit).
-In the examples below, the following table classes are used:
+No SQL is executed on the database when a ResultSet is created, it
+just stores all the conditions needed to create the query.
- package MyApp::Schema::Artist;
- use base qw/DBIx::Class/;
- __PACKAGE__->load_components(qw/Core/);
- __PACKAGE__->table('artist');
- __PACKAGE__->add_columns(qw/artistid name/);
- __PACKAGE__->set_primary_key('artistid');
- __PACKAGE__->has_many(cds => 'MyApp::Schema::CD');
- 1;
+A basic ResultSet representing the data of an entire table is returned
+by calling C<resultset> on a L<DBIx::Class::Schema> and passing in a
+L<Source|DBIx::Class::Manual::Glossary/Source> name.
- package MyApp::Schema::CD;
- use base qw/DBIx::Class/;
- __PACKAGE__->load_components(qw/Core/);
- __PACKAGE__->table('cd');
- __PACKAGE__->add_columns(qw/cdid artist title year/);
- __PACKAGE__->set_primary_key('cdid');
- __PACKAGE__->belongs_to(artist => 'MyApp::Schema::Artist');
- 1;
+ my $users_rs = $schema->resultset('User');
+
+A new ResultSet is returned from calling L</search> on an existing
+ResultSet. The new one will contain all the conditions of the
+original, plus any new conditions added in the C<search> call.
+
+A ResultSet is also an iterator. L</next> is used to return all the
+L<DBIx::Class::Row>s the ResultSet represents.
+
+The query that the ResultSet represents is B<only> executed against
+the database when these methods are called:
+
+=over
+
+=item L</find>
+
+=item L</next>
+
+=item L</all>
+
+=item L</count>
+
+=item L</single>
+
+=item L</first>
+
+=back
+
+=head1 EXAMPLES
+
+=head2 Chaining resultsets
+
+Let's say you've got a query that needs to be run to return some data
+to the user. But, you have an authorization system in place that
+prevents certain users from seeing certain information. So, you want
+to construct the basic query in one method, but add constraints to it in
+another.
+
+ sub get_data {
+ my $self = shift;
+ my $request = $self->get_request; # Get a request object somehow.
+ my $schema = $self->get_schema; # Get the DBIC schema object somehow.
+
+ my $cd_rs = $schema->resultset('CD')->search({
+ title => $request->param('title'),
+ year => $request->param('year'),
+ });
+
+ $self->apply_security_policy( $cd_rs );
+
+ return $cd_rs->all();
+ }
+
+ sub apply_security_policy {
+ my $self = shift;
+ my ($rs) = @_;
+
+ return $rs->search({
+ subversive => 0,
+ });
+ }
+
+=head2 Multiple queries
+
+Since a resultset just defines a query, you can do all sorts of
+things with it with the same object.
+
+ # Don't hit the DB yet.
+ my $cd_rs = $schema->resultset('CD')->search({
+ title => 'something',
+ year => 2009,
+ });
+
+ # Each of these hits the DB individually.
+ my $count = $cd_rs->count;
+ my $most_recent = $cd_rs->get_column('date_released')->max();
+ my @records = $cd_rs->all;
+
+And it's not just limited to SELECT statements.
+
+ $cd_rs->delete();
+
+This is even cooler:
+
+ $cd_rs->create({ artist => 'Fred' });
+
+Which is the same as:
+
+ $schema->resultset('CD')->create({
+ title => 'something',
+ year => 2009,
+ artist => 'Fred'
+ });
+
+See: L</search>, L</count>, L</get_column>, L</all>, L</create>.
=head1 OVERLOADING
sub single {
my ($self, $where) = @_;
+ if(@_ > 2) {
+ $self->throw_exception('single() only takes search conditions, no attributes. You want ->search( $cond, $attrs )->single()');
+ }
+
my $attrs = { %{$self->_resolved_attrs} };
if ($where) {
if (defined $attrs->{where}) {
=cut
sub all {
- my ($self) = @_;
+ my $self = shift;
+ if(@_) {
+ $self->throw_exception("all() doesn't take any arguments, you probably wanted ->search(...)->all()");
+ }
+
return @{ $self->get_cache } if $self->get_cache;
my @obj;
$self->throw_exception("Values for update must be a hash")
unless ref $values eq 'HASH';
+ carp( 'WARNING! Currently $rs->update() does not generate proper SQL'
+ . ' on joined resultsets, and may affect rows well outside of the'
+ . ' contents of $rs. Use at your own risk' )
+ if ( $self->{attrs}{seen_join} );
+
my $cond = $self->_cond_for_update_delete;
return $self->result_source->storage->update(
&& $self->{cond} eq $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION
) {
%new = %{$self->{attrs}{related_objects}};
+ $new{-from_resultset} = [ keys %new ] if keys %new;
} else {
$self->throw_exception(
"Can't abstract implicit construct, condition not a hash"
return \%unaliased;
}
+=head2 as_query
+
+=over 4
+
+=item Arguments: none
+
+=item Return Value: \[ $sql, @bind ]
+
+=back
+
+Returns the SQL query and bind vars associated with the invocant.
+
+This is generally used as the RHS for a subquery.
+
+=cut
+
+sub as_query { return shift->cursor->as_query(@_) }
+
=head2 find_or_new
=over 4
push(@{$attrs->{as}}, @$adds);
}
- $attrs->{from} ||= [ { 'me' => $source->from } ];
+ $attrs->{from} ||= [ { $self->{attrs}{alias} => $source->from } ];
if (exists $attrs->{join} || exists $attrs->{prefetch}) {
my $join = delete $attrs->{join} || {};
if (my $prefetch = delete $attrs->{prefetch}) {
$prefetch = $self->_merge_attr({}, $prefetch);
my @pre_order;
- my $seen = $attrs->{seen_join} || {};
+ my $seen = { %{ $attrs->{seen_join} || {} } };
foreach my $p (ref $prefetch eq 'ARRAY' ? @$prefetch : ($prefetch)) {
# bring joins back to level of current class
my @prefetch = $source->resolve_prefetch(
=head1 ATTRIBUTES
-The resultset takes various attributes that modify its behavior. Here's an
-overview of them:
+Attributes are used to refine a ResultSet in various ways when
+searching for data. They can be passed to any method which takes an
+C<\%attrs> argument. See L</search>, L</search_rs>, L</find>,
+L</count>.
+
+These are in no particular order:
=head2 order_by
=over 4
-Indicates additional column names for those added via L</+select>.
+Indicates additional column names for those added via L</+select>. See L</as>.
=back
return $new;
}
+=head2 as_query
+
+=over 4
+
+=item Arguments: none
+
+=item Return Value: \[ $sql, @bind ]
+
+=back
+
+Returns the SQL query and bind vars associated with the invocant.
+
+This is generally used as the RHS for a subquery.
+
+=cut
+
+sub as_query { return shift->_resultset->as_query }
+
=head2 next
=over 4
return shift->func('MIN');
}
+=head2 min_rs
+
+=over 4
+
+=item Arguments: none
+
+=item Return Value: $resultset
+
+=back
+
+ my $rs = $year_col->min_rs();
+
+Wrapper for ->func_rs for function MIN().
+
+=cut
+
+sub min_rs { return shift->func_rs('MIN') }
+
=head2 max
=over 4
return shift->func('MAX');
}
+=head2 max_rs
+
+=over 4
+
+=item Arguments: none
+
+=item Return Value: $resultset
+
+=back
+
+ my $rs = $year_col->max_rs();
+
+Wrapper for ->func_rs for function MAX().
+
+=cut
+
+sub max_rs { return shift->func_rs('MAX') }
+
=head2 sum
=over 4
return shift->func('SUM');
}
+=head2 sum_rs
+
+=over 4
+
+=item Arguments: none
+
+=item Return Value: $resultset
+
+=back
+
+ my $rs = $year_col->sum_rs();
+
+Wrapper for ->func_rs for function SUM().
+
+=cut
+
+sub sum_rs { return shift->func_rs('SUM') }
+
=head2 func
=over 4
sub func {
my ($self,$function) = @_;
- my $cursor = $self->{_parent_resultset}->search(undef, {select => {$function => $self->{_select}}, as => [$self->{_as}]})->cursor;
+ my $cursor = $self->func_rs($function)->cursor;
if( wantarray ) {
return map { $_->[ 0 ] } $cursor->all;
return ( $cursor->next )[ 0 ];
}
+=head2 func_rs
+
+=over 4
+
+=item Arguments: $function
+
+=item Return Value: $resultset
+
+=back
+
+Creates the resultset that C<func()> uses to run its query.
+
+=cut
+
+sub func_rs {
+ my ($self,$function) = @_;
+ return $self->{_parent_resultset}->search(
+ undef, {
+ select => {$function => $self->{_select}},
+ as => [$self->{_as}],
+ },
+ );
+}
+
=head2 throw_exception
See L<DBIx::Class::Schema/throw_exception> for details.
);
}
-
1;
=head1 AUTHORS
use Scalar::Util ();
use Scope::Guard;
+BEGIN {
+ *MULTICREATE_DEBUG =
+ $ENV{DBIC_MULTICREATE_DEBUG}
+ ? sub () { 1 }
+ : sub () { 0 };
+}
+
__PACKAGE__->mk_group_accessors('simple' => qw/_source_handle/);
=head1 NAME
$new->result_source($source);
}
+ if (my $related = delete $attrs->{-from_resultset}) {
+ @{$new->{_ignore_at_insert}={}}{@$related} = ();
+ }
+
if ($attrs) {
$new->throw_exception("attrs must be a hashref")
unless ref($attrs) eq 'HASH';
$rel_obj = $new->__new_related_find_or_new_helper($key, $rel_obj);
}
- $new->{_rel_in_storage} = 0 unless ($rel_obj->in_storage);
+ if ($rel_obj->in_storage) {
+ $new->set_from_related($key, $rel_obj);
+ } else {
+ $new->{_rel_in_storage} = 0;
+ MULTICREATE_DEBUG and warn "MC $new uninserted $key $rel_obj\n";
+ }
- $new->set_from_related($key, $rel_obj) if $rel_obj->in_storage;
$related->{$key} = $rel_obj;
next;
} elsif ($info && $info->{attrs}{accessor}
&& $info->{attrs}{accessor} eq 'multi'
&& ref $attrs->{$key} eq 'ARRAY') {
my $others = delete $attrs->{$key};
- foreach my $rel_obj (@$others) {
+ my $total = @$others;
+ my @objects;
+ foreach my $idx (0 .. $#$others) {
+ my $rel_obj = $others->[$idx];
if(!Scalar::Util::blessed($rel_obj)) {
$rel_obj = $new->__new_related_find_or_new_helper($key, $rel_obj);
}
- $new->{_rel_in_storage} = 0 unless ($rel_obj->in_storage);
+ if ($rel_obj->in_storage) {
+ $new->set_from_related($key, $rel_obj);
+ } else {
+ $new->{_rel_in_storage} = 0;
+ MULTICREATE_DEBUG and
+ warn "MC $new uninserted $key $rel_obj (${\($idx+1)} of $total)\n";
+ }
$new->set_from_related($key, $rel_obj) if $rel_obj->in_storage;
+ push(@objects, $rel_obj);
}
- $related->{$key} = $others;
+ $related->{$key} = \@objects;
next;
} elsif ($info && $info->{attrs}{accessor}
&& $info->{attrs}{accessor} eq 'filter')
if(!Scalar::Util::blessed($rel_obj)) {
$rel_obj = $new->__new_related_find_or_new_helper($key, $rel_obj);
}
- $new->{_rel_in_storage} = 0 unless ($rel_obj->in_storage);
+ unless ($rel_obj->in_storage) {
+ $new->{_rel_in_storage} = 0;
+ MULTICREATE_DEBUG and warn "MC $new uninserted $key $rel_obj";
+ }
$inflated->{$key} = $rel_obj;
next;
} elsif ($class->has_column($key)
$relname, { $rel_obj->get_columns }
);
+ MULTICREATE_DEBUG and warn "MC $self pre-inserting $relname $rel_obj\n";
+
$rel_obj->insert();
$self->set_from_related($relname, $rel_obj);
delete $related_stuff{$relname};
}
}
+ MULTICREATE_DEBUG and do {
+ no warnings 'uninitialized';
+ warn "MC $self inserting (".join(', ', $self->get_columns).")\n";
+ };
my $updated_cols = $source->storage->insert($source, { $self->get_columns });
foreach my $col (keys %$updated_cols) {
$self->store_column($col, $updated_cols->{$col});
if (@auto_pri) {
#$self->throw_exception( "More than one possible key found for auto-inc on ".ref $self )
# if defined $too_many;
-
+ MULTICREATE_DEBUG and warn "MC $self fetching missing PKs ".join(', ', @auto_pri)."\n";
my $storage = $self->result_source->storage;
$self->throw_exception( "Missing primary key but Storage doesn't support last_insert_id" )
unless $storage->can('last_insert_id');
$self->throw_exception( "Can't get last insert id" )
unless (@ids == @auto_pri);
$self->store_column($auto_pri[$_] => $ids[$_]) for 0 .. $#ids;
+#use Data::Dumper; warn Dumper($self);
}
+
$self->{_dirty_columns} = {};
$self->{related_resultsets} = {};
if(!$self->{_rel_in_storage}) {
- ## Now do the has_many rels, that need $selfs ID.
+ ## Now do the relationships that need our ID (has_many etc.)
foreach my $relname (keys %related_stuff) {
my $rel_obj = $related_stuff{$relname};
my @cands;
$obj->set_from_related($_, $self) for keys %$reverse;
my $them = { %{$obj->{_relationship_data} || {} }, $obj->get_inflated_columns };
if ($self->__their_pk_needs_us($relname, $them)) {
- $obj = $self->find_or_create_related($relname, $them);
+ if (exists $self->{_ignore_at_insert}{$relname}) {
+ MULTICREATE_DEBUG and warn "MC $self skipping post-insert on $relname";
+ } else {
+ MULTICREATE_DEBUG and warn "MC $self re-creating $relname $obj";
+ my $re = $self->find_or_create_related($relname, $them);
+ %{$obj} = %{$re};
+ MULTICREATE_DEBUG and warn "MC $self new $relname $obj";
+ }
} else {
+ MULTICREATE_DEBUG and warn "MC $self post-inserting $obj";
$obj->insert();
}
}
}
}
+ delete $self->{_ignore_at_insert};
$rollback_guard->commit;
}
{
my $rel = delete $upd->{$key};
$self->set_from_related($key => $rel);
- $self->{_relationship_data}{$key} = $rel;
+ $self->{_relationship_data}{$key} = $rel;
} elsif ($info && $info->{attrs}{accessor}
- && $info->{attrs}{accessor} eq 'multi'
- && ref $upd->{$key} eq 'ARRAY') {
- my $others = delete $upd->{$key};
- foreach my $rel_obj (@$others) {
- if(!Scalar::Util::blessed($rel_obj)) {
- $rel_obj = $self->create_related($key, $rel_obj);
- }
- }
- $self->{_relationship_data}{$key} = $others;
-# $related->{$key} = $others;
- next;
+ && $info->{attrs}{accessor} eq 'multi') {
+ $self->throw_exception(
+ "Recursive update is not supported over relationships of type multi ($key)"
+ );
}
elsif ($self->has_column($key)
&& exists $self->column_info($key)->{_inflate_info})
{
- $self->set_inflated_column($key, delete $upd->{$key});
+ $self->set_inflated_column($key, delete $upd->{$key});
}
}
}
return join('', @sqlf);
}
+sub _bind_to_sql {
+ my $self = shift;
+ my $arr = shift;
+ my $sql = shift @$$arr;
+ $sql =~ s/\?/$self->_quote((shift @$$arr)->[1])/eg;
+ return $sql
+}
+
sub _make_as {
my ($self, $from) = @_;
- return join(' ', map { (ref $_ eq 'SCALAR' ? $$_ : $self->_quote($_)) }
- reverse each %{$self->_skip_options($from)});
+ return join(' ', map { (ref $_ eq 'SCALAR' ? $$_
+ : ref $_ eq 'REF' ? $self->_bind_to_sql($_)
+ : $self->_quote($_))
+ } reverse each %{$self->_skip_options($from)});
}
sub _skip_options {
$self->_do_query($_) foreach @$action;
}
else {
- my @to_run = (ref $action eq 'ARRAY') ? (@$action) : ($action);
- $self->_query_start(@to_run);
- $self->_dbh->do(@to_run);
- $self->_query_end(@to_run);
+ # Most debuggers expect ($sql, @bind), so we need to exclude
+ # the attribute hash which is the second argument to $dbh->do
+ # furthermore the bind values are usually to be presented
+ # as named arrayref pairs, so wrap those here too
+ my @do_args = (ref $action eq 'ARRAY') ? (@$action) : ($action);
+ my $sql = shift @do_args;
+ my $attrs = shift @do_args;
+ my @bind = map { [ undef, $_ ] } @do_args;
+
+ $self->_query_start($sql, @bind);
+ $self->_dbh->do($sql, $attrs, @do_args);
+ $self->_query_end($sql, @bind);
}
return $self;
sub _prep_for_execute {
my ($self, $op, $extra_bind, $ident, $args) = @_;
+ if( blessed($ident) && $ident->isa("DBIx::Class::ResultSource") ) {
+ $ident = $ident->from();
+ }
+
my ($sql, @bind) = $self->sql_maker->$op($ident, @$args);
+
unshift(@bind,
map { ref $_ eq 'ARRAY' ? $_ : [ '!!dummy', $_ ] } @$extra_bind)
if $extra_bind;
-
return ($sql, \@bind);
}
sub _dbh_execute {
my ($self, $dbh, $op, $extra_bind, $ident, $bind_attributes, @args) = @_;
- if( blessed($ident) && $ident->isa("DBIx::Class::ResultSource") ) {
- $ident = $ident->from();
- }
-
my ($sql, $bind) = $self->_prep_for_execute($op, $extra_bind, $ident, \@args);
$self->_query_start( $sql, @$bind );
}
sub _select {
+ my $self = shift;
+ my $sql_maker = $self->sql_maker;
+ local $sql_maker->{for};
+ return $self->_execute($self->_select_args(@_));
+}
+
+sub _select_args {
my ($self, $ident, $select, $condition, $attrs) = @_;
my $order = $attrs->{order_by};
my $for = delete $attrs->{for};
my $sql_maker = $self->sql_maker;
- local $sql_maker->{for} = $for;
+ $sql_maker->{for} = $for;
if (exists $attrs->{group_by} || $attrs->{having}) {
$order = {
push @args, $attrs->{rows}, $attrs->{offset};
}
- return $self->_execute(@args);
+ return @args;
}
sub source_bind_attributes {
sub deploy {
my ($self, $schema, $type, $sqltargs, $dir) = @_;
- foreach my $statement ( $self->deployment_statements($schema, $type, undef, $dir, { no_comments => 1, %{ $sqltargs || {} } } ) ) {
- foreach my $line ( split(";\n", $statement)) {
- next if($line =~ /^--/);
- next if(!$line);
-# next if($line =~ /^DROP/m);
- next if($line =~ /^BEGIN TRANSACTION/m);
- next if($line =~ /^COMMIT/m);
- next if $line =~ /^\s+$/; # skip whitespace only
- $self->_query_start($line);
- eval {
- $self->dbh->do($line); # shouldn't be using ->dbh ?
- };
- if ($@) {
- warn qq{$@ (running "${line}")};
- }
- $self->_query_end($line);
+ my $deploy = sub {
+ my $line = shift;
+ return if($line =~ /^--/);
+ return if(!$line);
+ # next if($line =~ /^DROP/m);
+ return if($line =~ /^BEGIN TRANSACTION/m);
+ return if($line =~ /^COMMIT/m);
+ return if $line =~ /^\s+$/; # skip whitespace only
+ $self->_query_start($line);
+ eval {
+ $self->dbh->do($line); # shouldn't be using ->dbh ?
+ };
+ if ($@) {
+ warn qq{$@ (running "${line}")};
+ }
+ $self->_query_end($line);
+ };
+ my @statements = $self->deployment_statements($schema, $type, undef, $dir, { no_comments => 1, %{ $sqltargs || {} } } );
+ if (@statements > 1) {
+ foreach my $statement (@statements) {
+ $deploy->( $statement );
+ }
+ }
+ elsif (@statements == 1) {
+ foreach my $line ( split(";\n", $statements[0])) {
+ $deploy->( $line );
}
}
}
return bless ($new, $class);
}
+=head2 as_query
+
+=over 4
+
+=item Arguments: none
+
+=item Return Value: \[ $sql, @bind ]
+
+=back
+
+Returns the SQL statement and bind vars associated with the invocant.
+
+=cut
+
+sub as_query {
+ my $self = shift;
+
+ my $storage = $self->{storage};
+ my $sql_maker = $storage->sql_maker;
+ local $sql_maker->{for};
+
+ my @args = $storage->_select_args(@{$self->{args}});
+ my ($sql, $bind) = $storage->_prep_for_execute(@args[0 .. 2], [@args[4 .. $#args]]);
+ return \[ "($sql)", @$bind ];
+}
+
=head2 next
=over 4
# trigger_body is a LONG
$dbh->{LongReadLen} = 64 * 1024 if ($dbh->{LongReadLen} < 64 * 1024);
- my $sth = $dbh->prepare($sql);
- $sth->execute( uc($source->name) );
+ my $sth;
+
+ # check for fully-qualified name (eg. SCHEMA.TABLENAME)
+ if ( my ( $schema, $table ) = $source->name =~ /(\w+)\.(\w+)/ ) {
+ $sql = q{
+ SELECT trigger_body FROM ALL_TRIGGERS t
+ WHERE t.owner = ? AND t.table_name = ?
+ AND t.triggering_event = 'INSERT'
+ AND t.status = 'ENABLED'
+ };
+ $sth = $dbh->prepare($sql);
+ $sth->execute( uc($schema), uc($table) );
+ }
+ else {
+ $sth = $dbh->prepare($sql);
+ $sth->execute( uc( $source->name ) );
+ }
while (my ($insert_trigger) = $sth->fetchrow_array) {
return uc($1) if $insert_trigger =~ m!(\w+)\.nextval!i; # col name goes here???
}
return $id;
}
+=head2 connected
+
+Returns true if we have an open (and working) database connection, false if it is not (yet)
+open (or does not work). (Executes a simple SELECT to make sure it works.)
+
+The reason this is needed is that L<DBD::Oracle>'s ping() does not do a real
+OCIPing but just gets the server version, which doesn't help if someone killed
+your session.
+
+=cut
+
+sub connected {
+ my $self = shift;
+
+ if (not $self->SUPER::connected(@_)) {
+ return 0;
+ }
+ else {
+ my $dbh = $self->_dbh;
+
+ local $dbh->{RaiseError} = 1;
+
+ eval {
+ my $ping_sth = $dbh->prepare_cached("select 1 from dual");
+ $ping_sth->execute;
+ $ping_sth->finish;
+ };
+
+ return $@ ? 0 : 1;
+ }
+}
+
+sub _dbh_execute {
+ my $self = shift;
+ my ($dbh, $op, $extra_bind, $ident, $bind_attributes, @args) = @_;
+
+ my $wantarray = wantarray;
+
+ my (@res, $exception, $retried);
+
+ RETRY: {
+ do {
+ eval {
+ if ($wantarray) {
+ @res = $self->SUPER::_dbh_execute(@_);
+ } else {
+ $res[0] = $self->SUPER::_dbh_execute(@_);
+ }
+ };
+ $exception = $@;
+ if ($exception =~ /ORA-01003/) {
+ # ORA-01003: no statement parsed (someone changed the table somehow,
+ # invalidating your cursor.)
+ my ($sql, $bind) = $self->_prep_for_execute($op, $extra_bind, $ident, \@args);
+ delete $dbh->{CachedKids}{$sql};
+ } else {
+ last RETRY;
+ }
+ } while (not $retried++);
+ }
+
+ $self->throw_exception($exception) if $exception;
+
+ wantarray ? @res : $res[0]
+}
+
=head2 get_autoinc_seq
Returns the sequence name for an autoincrement column
use strict;
use warnings;
-use vars qw($DEBUG @EXPORT_OK);
+use vars qw($DEBUG $VERSION @EXPORT_OK);
+$VERSION = '1.10';
$DEBUG = 0 unless defined $DEBUG;
use Exporter;
=head1 SYNOPSIS
+ ## Via DBIx::Class
+ use MyApp::Schema;
+ my $schema = MyApp::Schema->connect("dbi:SQLite:something.db");
+ $schema->create_ddl_dir();
+ ## or
+ $schema->deploy();
+
+ ## Standalone
use MyApp::Schema;
use SQL::Translator;
=head1 DESCRIPTION
+This class requires L<SQL::Translator> installed to work.
+
C<SQL::Translator::Parser::DBIx::Class> reads a DBIx::Class schema,
interrogates the columns, and stuffs it all in an $sqlt_schema object.
+It's primary use is in deploying database layouts described as a set
+of L<DBIx::Class> classes, to a database. To do this, see the
+L<DBIx::Class::Schema/deploy> method.
+
+This can also be achieved by having DBIx::Class export the schema as a
+set of SQL files ready for import into your database, or passed to
+other machines that need to have your application installed but don't
+have SQL::Translator installed. To do this see the
+L<DBIx::Class::Schema/create_ddl_dir> method.
+
=head1 SEE ALSO
-SQL::Translator.
+L<SQL::Translator>, L<DBIx::Class::Schema>
=head1 AUTHORS
mk_classaccessor/
]
},
+ 'DBIx::Class::Row' => {
+ ignore => [
+ qw( MULTICREATE_DEBUG )
+ ],
+ },
'DBIx::Class::Storage' => {
ignore => [
qw(cursor)
$undef_artist_cd->related_resultset('artist')->new({name => 'foo'});
};
is( $@, '', "Object created on a resultset related to not yet inserted object");
-
+lives_ok{
+ $schema->resultset('Artwork')->new_result({})->cd;
+} 'undef_on_null_fk does not choke on empty conds';
+
my $def_artist_cd = $schema->resultset("CD")->new_result({ 'title' => 'badgers', 'year' => 2007, artist => undef });
is($def_artist_cd->has_column_loaded('artist'), 1, 'FK loaded');
is($def_artist_cd->search_related('artist')->count, 0, 'closed search on null FK');
cmp_ok($artist->cds->count, '==', 0, "Correct new #cds for artist");
cmp_ok($nartist->cds->count, '==', 2, "Correct new #cds for artist");
-my $new_artist = $schema->resultset("Artist")->new_result({ 'name' => 'Depeche Mode' });
-my $new_related_cd = $new_artist->new_related('cds', { 'title' => 'Leave in Silence', 'year' => 1982});
-eval {
- $new_artist->insert;
- $new_related_cd->insert;
-};
-is ($@, '', 'Staged insertion successful');
-ok($new_artist->in_storage, 'artist inserted');
-ok($new_related_cd->in_storage, 'new_related_cd inserted');
-
-my $new_cd = $schema->resultset("CD")->new_result({});
-my $new_related_artist = $new_cd->new_related('artist', { 'name' => 'Marillion',});
-lives_ok (
- sub {
- $new_related_artist->insert;
- $new_cd->title( 'Misplaced Childhood' );
- $new_cd->year ( 1985 );
-# $new_cd->artist( $new_related_artist ); # For exact backward compatibility # not sure what this means
- $new_cd->insert;
- },
- 'Reversed staged insertion successful'
-);
-ok($new_related_artist->in_storage, 'related artist inserted');
-ok($new_cd->in_storage, 'cd inserted');
-
# check if is_foreign_key_constraint attr is set
my $rs_normal = $schema->source('Track');
my $relinfo = $rs_normal->relationship_info ('cd');
+{
+ package # hide from PAUSE
+ DBICTest::Schema::ArtistFQN;
+
+ use base 'DBIx::Class::Core';
+
+ __PACKAGE__->table(
+ defined $ENV{DBICTEST_ORA_USER}
+ ? $ENV{DBICTEST_ORA_USER} . '.artist'
+ : 'artist'
+ );
+ __PACKAGE__->add_columns(
+ 'artistid' => {
+ data_type => 'integer',
+ is_auto_increment => 1,
+ },
+ 'name' => {
+ data_type => 'varchar',
+ size => 100,
+ is_nullable => 1,
+ },
+ );
+ __PACKAGE__->set_primary_key('artistid');
+
+ 1;
+}
+
use strict;
use warnings;
' as well as following sequences: \'pkid1_seq\', \'pkid2_seq\' and \'nonpkid_seq\''
unless ($dsn && $user && $pass);
-plan tests => 23;
+plan tests => 24;
+DBICTest::Schema->load_classes('ArtistFQN');
my $schema = DBICTest::Schema->connect($dsn, $user, $pass);
my $dbh = $schema->storage->dbh;
my $new = $schema->resultset('Artist')->create({ name => 'foo' });
is($new->artistid, 1, "Oracle Auto-PK worked");
+# test again with fully-qualified table name
+$new = $schema->resultset('ArtistFQN')->create( { name => 'bar' } );
+is( $new->artistid, 2, "Oracle Auto-PK worked with fully-qualified tablename" );
+
# test join with row count ambiguity
my $cd = $schema->resultset('CD')->create({ cdid => 1, artist => 1, title => 'EP C', year => '2003' });
my $track = $schema->resultset('Track')->create({ trackid => 1, cd => 1, position => 1, title => 'Track1' });
}
my $it = $schema->resultset('Artist')->search( {},
{ rows => 3,
- offset => 2,
+ offset => 3,
order_by => 'artistid' }
);
is( $it->count, 3, "LIMIT count ok" );
# clean up our mess
END {
- if($dbh) {
+ if($schema && ($dbh = $schema->storage->dbh)) {
$dbh->do("DROP SEQUENCE artist_seq");
$dbh->do("DROP SEQUENCE pkid1_seq");
$dbh->do("DROP SEQUENCE pkid2_seq");
eval "use DBD::SQLite";
plan $@
? ( skip_all => 'needs DBD::SQLite for testing' )
- : ( tests => 16 );
+ : ( tests => 18 );
}
# figure out if we've got a version of sqlite that is older than 3.2.6, in
is($rs->first->name, 'We Are Goth', 'Correct record returned');
+# test for warnings on delete of joined resultset
+$rs = $schema->resultset("CD")->search(
+ { 'artist.name' => 'Caterwauler McCrae' },
+ { join => [qw/artist/]}
+);
+my $tst_delete_warning;
+eval {
+ local $SIG{__WARN__} = sub { $tst_delete_warning = shift };
+ $rs->delete();
+};
+
+ok( ($@ || $tst_delete_warning), 'fail/warning on attempt to delete a join-ed resultset');
+
+# test for warnings on update of joined resultset
+$rs = $schema->resultset("CD")->search(
+ { 'artist.name' => 'Random Boy Band' },
+ { join => [qw/artist/]}
+);
+my $tst_update_warning;
+eval {
+ local $SIG{__WARN__} = sub { $tst_update_warning = shift };
+ $rs->update({ 'artist' => 1 });
+};
+
+ok( ($@ || $tst_update_warning), 'fail/warning on attempt to update a join-ed resultset');
use warnings;
use Test::More;
+use Test::Exception;
use lib qw(t/lib);
use DBICTest;
use Data::Dumper;
eval "use DBD::SQLite";
plan $@
? ( skip_all => 'needs DBD::SQLite for testing' )
- : ( tests => 58 );
+ : ( tests => 68 );
}
# figure out if we've got a version of sqlite that is older than 3.2.6, in
is(Dumper($attr), $attr_str, 'Attribute hash untouched after search()');
cmp_ok($rs + 0, '==', 3, 'Correct number of records returned');
+# A search() with prefetch seems to pollute an already joined resultset
+# in a way that offsets future joins (adapted from a test case by Debolaz)
+{
+ my ($cd_rs, $attrs);
+
+ # test a real-life case - rs is obtained by an implicit m2m join
+ $cd_rs = $schema->resultset ('Producer')->first->cds;
+ $attrs = Dumper $cd_rs->{attrs};
+
+ $cd_rs->search ({})->all;
+ is (Dumper ($cd_rs->{attrs}), $attrs, 'Resultset attributes preserved after a simple search');
+
+ lives_ok (sub {
+ $cd_rs->search ({'artist.artistid' => 1}, { prefetch => 'artist' })->all;
+ is (Dumper ($cd_rs->{attrs}), $attrs, 'Resultset attributes preserved after search with prefetch');
+ }, 'first prefetching search ok');
+
+ lives_ok (sub {
+ $cd_rs->search ({'artist.artistid' => 1}, { prefetch => 'artist' })->all;
+ is (Dumper ($cd_rs->{attrs}), $attrs, 'Resultset attributes preserved after another search with prefetch')
+ }, 'second prefetching search ok');
+
+
+ # test a regular rs with an empty seen_join injected - it should still work!
+ $cd_rs = $schema->resultset ('CD');
+ $cd_rs->{attrs}{seen_join} = {};
+ $attrs = Dumper $cd_rs->{attrs};
+
+ $cd_rs->search ({})->all;
+ is (Dumper ($cd_rs->{attrs}), $attrs, 'Resultset attributes preserved after a simple search');
+
+ lives_ok (sub {
+ $cd_rs->search ({'artist.artistid' => 1}, { prefetch => 'artist' })->all;
+ is (Dumper ($cd_rs->{attrs}), $attrs, 'Resultset attributes preserved after search with prefetch');
+ }, 'first prefetching search ok');
+
+ lives_ok (sub {
+ $cd_rs->search ({'artist.artistid' => 1}, { prefetch => 'artist' })->all;
+ is (Dumper ($cd_rs->{attrs}), $attrs, 'Resultset attributes preserved after another search with prefetch')
+ }, 'second prefetching search ok');
+}
+
+
my $queries = 0;
$schema->storage->debugcb(sub { $queries++; });
$schema->storage->debug(1);
});
$guard->commit;
- } qr/No such column made_up_column .*? at .*?81transactions.t line \d+/, "Error propogated okay";
+ } qr/No such column made_up_column .*? at .*?81transactions.t line \d+/s, "Error propogated okay";
ok(!$artist_rs->find({name => 'Death Cab for Cutie'}), "Artist not created");
# the 'dummy' table
ok( !defined($tschema->get_table('dummy')), "Dummy table was removed by hook");
+# Test that the Artist resultsource sqlt_deploy_hook was called okay and added
+# an index
+SKIP: {
+ skip ('Artist sqlt_deploy_hook is only called with an SQLite backend', 1)
+ if $schema->storage->sqlt_type ne 'SQLite';
+
+ ok( ( grep
+ { $_->name eq 'artist_name_hookidx' }
+ $tschema->get_table('artist')->get_indices
+ ), 'sqlt_deploy_hook fired within a resultsource');
+}
+
# Test that nonexistent constraints are not found
my $constraint = get_constraint('FOREIGN KEY', 'cd', ['title'], 'cd', ['year']);
ok( !defined($constraint), 'nonexistent FOREIGN KEY constraint not found' );
use lib qw(t/lib);
use DBICTest;
-plan tests => 89;
+plan tests => 77;
my $schema = DBICTest->init_schema();
-# simple create + parent (the stuff $rs belongs_to)
+diag '* simple create + parent (the stuff $rs belongs_to)';
eval {
my $cd = $schema->resultset('CD')->create({
artist => {
};
diag $@ if $@;
-# same as above but the child and parent have no values,
-# except for an explicit parent pk
+diag '* same as above but the child and parent have no values, except for an explicit parent pk';
eval {
my $bm_rs = $schema->resultset('Bookmark');
my $bookmark = $bm_rs->create({
};
diag $@ if $@;
-# create over > 1 levels of has_many create (A => { has_many => { B => has_many => C } } )
+diag '* create over > 1 levels of has_many create (A => { has_many => { B => has_many => C } } )';
eval {
my $artist = $schema->resultset('Artist')->first;
my $cd = $artist->create_related (cds => {
'create via update of multi relationships throws an exception'
);
-# Create m2m while originating in the linker table
+diag '* Create m2m while originating in the linker table';
eval {
my $artist = $schema->resultset('Artist')->first;
my $c2p = $schema->resultset('CD_to_Producer')->create ({
};
diag $@ if $@;
-# create over > 1 levels of might_have (A => { might_have => { B => has_many => C } } )
+diag (<<'DG');
+* Create over > 1 levels of might_have with multiple has_many and multiple m2m
+but starting at a has_many level
+
+CD -> has_many -> Tracks -> might have -> Single -> has_many -> Tracks
+ \
+ \-> has_many \
+ --> CD2Producer
+ /-> has_many /
+ /
+ Producer
+DG
+
eval {
my $artist = $schema->resultset('Artist')->first;
my $cd = $schema->resultset('CD')->create ({
year => 2008,
tracks => [
{
- position => 1,
+ position => 1, # some day me might test this with Ordered
title => 'Off by one again',
},
{
producer => {
name => 'K&R',
}
- }
+ },
+ {
+ producer => {
+ name => 'Don Knuth',
+ }
+ },
]
},
},
is ($single->tracks->find ({ position => 1})->title, 'The dereferencer', 'Correct 1st track title');
is ($single->tracks->find ({ position => 2})->title, 'The dereferencer II', 'Correct 2nd track title');
- is ($single->cd_to_producer->count, 1, 'One producer created with the single cd');
- is ($single->cd_to_producer->first->producer->name, 'K&R', 'Producer name correct');
+ is ($single->cd_to_producer->count, 2, 'Two producers created for the single cd');
+ is_deeply (
+ [ sort map { $_->producer->name } ($single->cd_to_producer->all) ],
+ ['Don Knuth', 'K&R'],
+ 'Producers named correctly',
+ );
+};
+diag $@ if $@;
+
+diag (<<'DG');
+* Same as above but starting at the might_have directly
+
+Track -> might have -> Single -> has_many -> Tracks
+ \
+ \-> has_many \
+ --> CD2Producer
+ /-> has_many /
+ /
+ Producer
+DG
+
+eval {
+ my $cd = $schema->resultset('CD')->first;
+ my $track = $schema->resultset('Track')->create ({
+ cd => $cd,
+ position => 77, # some day me might test this with Ordered
+ title => 'Multicreate rocks',
+ cd_single => {
+ artist => $cd->artist,
+ year => 2008,
+ title => 'Disemboweling MultiCreate',
+ tracks => [
+ { title => 'Why does mst write this way', position => 1 },
+ { title => 'Chainsaw celebration', position => 2 },
+ { title => 'Purl cleans up', position => 3 },
+ ],
+ cd_to_producer => [
+ {
+ producer => {
+ name => 'mst',
+ }
+ },
+ {
+ producer => {
+ name => 'castaway',
+ }
+ },
+ {
+ producer => {
+ name => 'theorbtwo',
+ }
+ },
+ ]
+ },
+ });
+
+ isa_ok ($track, 'DBICTest::Track', 'Main Track object created');
+ is ($track->title, 'Multicreate rocks', 'Correct Track title');
+
+ my $single = $track->cd_single;
+ isa_ok ($single, 'DBICTest::CD', 'Created a single with the track');
+ is ($single->tracks->count, 3, '3 tracks on single CD');
+ is ($single->tracks->find ({ position => 1})->title, 'Why does mst write this way', 'Correct 1st track title');
+ is ($single->tracks->find ({ position => 2})->title, 'Chainsaw celebration', 'Correct 2nd track title');
+ is ($single->tracks->find ({ position => 3})->title, 'Purl cleans up', 'Correct 3rd track title');
+
+ is ($single->cd_to_producer->count, 3, '3 producers created for the single cd');
+ is_deeply (
+ [ sort map { $_->producer->name } ($single->cd_to_producer->all) ],
+ ['castaway', 'mst', 'theorbtwo'],
+ 'Producers named correctly',
+ );
};
diag $@ if $@;
-# test might_have again but with a PK == FK in the middle (obviously not specified)
+diag '* Test might_have again but with a PK == FK in the middle (obviously not specified)';
eval {
my $artist = $schema->resultset('Artist')->first;
my $cd = $schema->resultset('CD')->create ({
'Images named correctly in objects',
);
-
my $artwork = $schema->resultset('Artwork')->search (
{ 'cd.title' => 'Music to code by at twilight' },
{ join => 'cd' },
};
diag $@ if $@;
-# test might_have again but with just a PK and FK (neither specified) in the mid-table
+diag '* Test might_have again but with just a PK and FK (neither specified) in the mid-table';
eval {
my $cd = $schema->resultset('CD')->first;
my $track = $schema->resultset ('Track')->create ({
};
diag $@ if $@;
-# nested find_or_create
+diag (<<'DG');
+* Test a multilevel might-have with a PK == FK in the might_have/has_many table
+
+CD -> might have -> Artwork
+ \
+ \-> has_many \
+ --> Artwork_to_Artist
+ /-> has_many /
+ /
+ Artist
+DG
+
+eval {
+ my $someartist = $schema->resultset('Artist')->first;
+ my $cd = $schema->resultset('CD')->create ({
+ artist => $someartist,
+ title => 'Music to code by until the cows come home',
+ year => 2008,
+ artwork => {
+ artwork_to_artist => [
+ { artist => { name => 'cowboy joe' } },
+ { artist => { name => 'billy the kid' } },
+ ],
+ },
+ });
+
+ isa_ok ($cd, 'DBICTest::CD', 'Main CD object created');
+ is ($cd->title, 'Music to code by until the cows come home', 'Correct CD title');
+
+ my $art_obj = $cd->artwork;
+ ok ($art_obj->has_column_loaded ('cd_id'), 'PK/FK present on artwork object');
+ is ($art_obj->artists->count, 2, 'Correct artwork creator count via the new object');
+ is_deeply (
+ [ sort $art_obj->artists->get_column ('name')->all ],
+ [ 'billy the kid', 'cowboy joe' ],
+ 'Artists named correctly when queried via object',
+ );
+
+ my $artwork = $schema->resultset('Artwork')->search (
+ { 'cd.title' => 'Music to code by until the cows come home' },
+ { join => 'cd' },
+ )->single;
+ is ($artwork->artists->count, 2, 'Correct artwork creator count via a new search');
+ is_deeply (
+ [ sort $artwork->artists->get_column ('name')->all ],
+ [ 'billy the kid', 'cowboy joe' ],
+ 'Artists named correctly queried via a new search',
+ );
+};
+diag $@ if $@;
+
+diag '* Nested find_or_create';
eval {
my $newartist2 = $schema->resultset('Artist')->find_or_create({
name => 'Fred 3',
};
diag $@ if $@;
-# multiple same level has_many create
+diag '* Multiple same level has_many create';
eval {
my $artist2 = $schema->resultset('Artist')->create({
name => 'Fred 4',
};
diag $@ if $@;
-# first create_related pass
+diag '* First create_related pass';
eval {
my $artist = $schema->resultset('Artist')->first;
};
diag $@ if $@;
-# second create_related with same arguments
+diag '* second create_related with same arguments';
eval {
my $artist = $schema->resultset('Artist')->first;
};
diag $@ if $@;
-# create of parents of a record linker table
+diag '* create of parents of a record linker table';
eval {
my $cdp = $schema->resultset('CD_to_Producer')->create({
cd => { artist => 1, title => 'foo', year => 2000 },
};
diag $@ if $@;
-#SPECIAL_CASE
eval {
my $kurt_cobain = { name => 'Kurt Cobain' };
};
diag $@ if $@;
-#SPECIAL_CASE2
+=pod
+# This test case has been moved to t/96multi_create/cd_single.t
eval {
my $pink_floyd = { name => 'Pink Floyd' };
is($a->cds && $a->cds->first->title, 'The Wall', 'CD insertion ok');
};
diag $@ if $@;
+=cut
+diag '* Create foreign key col obj including PK (See test 20 in 66relationships.t)';
## Create foreign key col obj including PK
## See test 20 in 66relationships.t
eval {
};
is($@, '', 'new cd created without clash on related artist');
-# Make sure exceptions from errors in created rels propogate
+diag '* Make sure exceptions from errors in created rels propogate';
eval {
my $t = $schema->resultset("Track")->new({ cd => { artist => undef } });
#$t->cd($t->new_related('cd', { artist => undef } ) );
};
like($@, qr/cd.artist may not be NULL/, "Exception propogated properly");
-# Test multi create over many_to_many
+diag '* Test multi create over many_to_many';
eval {
$schema->resultset('CD')->create ({
artist => {
is ($m2m_cd->first->producers->first->name, 'Cowboy Neal', 'Correct producer row created');
};
-# and some insane multicreate
-# (should work, despite the fact that no one will probably use it this way)
-
-# first count how many rows do we initially have
-my $counts;
-$counts->{$_} = $schema->resultset($_)->count for qw/Artist CD Genre Producer Tag/;
-
-# do the crazy create
-eval {
- $schema->resultset('CD')->create ({
- artist => {
- name => 'james',
- },
- title => 'Greatest hits 1',
- year => '2012',
- genre => {
- name => '"Greatest" collections',
- },
- tags => [
- { tag => 'A' },
- { tag => 'B' },
- ],
- cd_to_producer => [
- {
- producer => {
- name => 'bob',
- producer_to_cd => [
- {
- cd => {
- artist => {
- name => 'lars',
- cds => [
- {
- title => 'Greatest hits 2',
- year => 2012,
- genre => {
- name => '"Greatest" collections',
- },
- tags => [
- { tag => 'A' },
- { tag => 'B' },
- ],
- # This cd is created via artist so it doesn't know about producers
- cd_to_producer => [
- # if we specify 'bob' here things bomb
- # as the producer attached to Greatest Hits 1 is
- # already created, but not yet inserted.
- # Maybe this can be fixed, but things are hairy
- # enough already.
- #
- #{ producer => { name => 'bob' } },
- { producer => { name => 'paul' } },
- { producer => {
- name => 'flemming',
- producer_to_cd => [
- { cd => {
- artist => {
- name => 'kirk',
- cds => [
- {
- title => 'Greatest hits 3',
- year => 2012,
- genre => {
- name => '"Greatest" collections',
- },
- tags => [
- { tag => 'A' },
- { tag => 'B' },
- ],
- },
- {
- title => 'Greatest hits 4',
- year => 2012,
- genre => {
- name => '"Greatest" collections2',
- },
- tags => [
- { tag => 'A' },
- { tag => 'B' },
- ],
- },
- ],
- },
- title => 'Greatest hits 5',
- year => 2013,
- genre => {
- name => '"Greatest" collections2',
- },
- }},
- ],
- }},
- ],
- },
- ],
- },
- title => 'Greatest hits 6',
- year => 2012,
- genre => {
- name => '"Greatest" collections',
- },
- tags => [
- { tag => 'A' },
- { tag => 'B' },
- ],
- },
- },
- {
- cd => {
- artist => {
- name => 'lars', # should already exist
- # even though the artist 'name' is not uniquely constrained
- # find_or_create will arguably DWIM
- },
- title => 'Greatest hits 7',
- year => 2013,
- },
- },
- ],
- },
- },
- ],
- });
-
- is ($schema->resultset ('Artist')->count, $counts->{Artist} + 3, '3 new artists created');
- is ($schema->resultset ('Genre')->count, $counts->{Genre} + 2, '2 additional genres created');
- is ($schema->resultset ('Producer')->count, $counts->{Producer} + 3, '3 new producer');
- is ($schema->resultset ('CD')->count, $counts->{CD} + 7, '7 new CDs');
- is ($schema->resultset ('Tag')->count, $counts->{Tag} + 10, '10 new Tags');
-
- my $cd_rs = $schema->resultset ('CD')
- ->search ({ title => { -like => 'Greatest hits %' }}, { order_by => 'title'} );
- is ($cd_rs->count, 7, '7 greatest hits created');
-
- my $cds_2012 = $cd_rs->search ({ year => 2012});
- is ($cds_2012->count, 5, '5 CDs created in 2012');
-
- is (
- $cds_2012->search(
- { 'tags.tag' => { -in => [qw/A B/] } },
- { join => 'tags', group_by => 'me.cdid' }
- ),
- 5,
- 'All 10 tags were pairwise distributed between 5 year-2012 CDs'
- );
-
- my $paul_prod = $cd_rs->search (
- { 'producer.name' => 'paul'},
- { join => { cd_to_producer => 'producer' } }
- );
- is ($paul_prod->count, 1, 'Paul had 1 production');
- my $pauls_cd = $paul_prod->single;
- is ($pauls_cd->cd_to_producer->count, 2, 'Paul had one co-producer');
- is (
- $pauls_cd->search_related ('cd_to_producer',
- { 'producer.name' => 'flemming'},
- { join => 'producer' }
- )->count,
- 1,
- 'The second producer is flemming',
- );
-
- my $kirk_cds = $cd_rs->search ({ 'artist.name' => 'kirk' }, { join => 'artist' });
- is ($kirk_cds, 3, 'Kirk had 3 CDs');
- is (
- $kirk_cds->search (
- { 'cd_to_producer.cd' => { '!=', undef } },
- { join => 'cd_to_producer' },
- ),
- 1,
- 'Kirk had a producer only on one cd',
- );
-
- my $lars_cds = $cd_rs->search ({ 'artist.name' => 'lars' }, { join => 'artist' });
- is ($lars_cds->count, 3, 'Lars had 3 CDs');
- is (
- $lars_cds->search (
- { 'cd_to_producer.cd' => undef },
- { join => 'cd_to_producer' },
- ),
- 0,
- 'Lars always had a producer',
- );
- is (
- $lars_cds->search_related ('cd_to_producer',
- { 'producer.name' => 'flemming'},
- { join => 'producer' }
- )->count,
- 1,
- 'Lars produced 1 CD with flemming',
- );
- is (
- $lars_cds->search_related ('cd_to_producer',
- { 'producer.name' => 'bob'},
- { join => 'producer' }
- )->count,
- 2,
- 'Lars produced 2 CDs with bob',
- );
-
- my $bob_prod = $cd_rs->search (
- { 'producer.name' => 'bob'},
- { join => { cd_to_producer => 'producer' } }
- );
- is ($bob_prod->count, 3, 'Bob produced a total of 3 CDs');
-
- is (
- $bob_prod->search ({ 'artist.name' => 'james' }, { join => 'artist' })->count,
- 1,
- "Bob produced james' only CD",
- );
-};
-diag $@ if $@;
-
1;
--- /dev/null
+use strict;
+use warnings;
+
+use Test::More qw(no_plan);
+use Test::Exception;
+use lib qw(t/lib);
+use DBICTest;
+
+my $schema = DBICTest->init_schema();
+
+eval {
+ my $cd = $schema->resultset('CD')->first;
+ my $track = $schema->resultset('Track')->new_result({
+ cd => $cd,
+ position => 77, # some day me might test this with Ordered
+ title => 'Multicreate rocks',
+ cd_single => {
+ artist => $cd->artist,
+ year => 2008,
+ title => 'Disemboweling MultiCreate',
+ },
+ });
+
+ isa_ok ($track, 'DBICTest::Track', 'Main Track object created');
+
+ $track->insert;
+
+ ok(1, 'created track');
+
+ is($track->title, 'Multicreate rocks', 'Correct Track title');
+
+ my $single = $track->cd_single;
+
+ ok($single->cdid, 'Got cdid');
+};
--- /dev/null
+use strict;
+use warnings;
+
+use Test::More;
+use Test::Exception;
+use lib qw(t/lib);
+use DBICTest;
+
+plan tests => 9;
+
+my $schema = DBICTest->init_schema();
+
+# Test various new() invocations - this is all about backcompat, making
+# sure that insert() still works as expected by legacy code.
+#
+# What we essentially do is multi-instantiate objects, making sure nothing
+# gets inserted. Then we add some more objects to the mix either via
+# new_related() or by setting an accessor directly (or both) - again
+# expecting no inserts. Then after calling insert() on the starter object
+# we expect everything supplied to new() to get inserted, as well as any
+# relations whose PK's are necessary to complete the objects supplied
+# to new(). All other objects should be insert()able afterwards too.
+
+
+{
+ my $new_artist = $schema->resultset("Artist")->new_result({ 'name' => 'Depeche Mode' });
+ my $new_related_cd = $new_artist->new_related('cds', { 'title' => 'Leave in Silence', 'year' => 1982});
+ eval {
+ $new_artist->insert;
+ $new_related_cd->insert;
+ };
+ is ($@, '', 'Staged insertion successful');
+ ok($new_artist->in_storage, 'artist inserted');
+ ok($new_related_cd->in_storage, 'new_related_cd inserted');
+}
+
+{
+ my $new_artist = $schema->resultset("Artist")->new_result({ 'name' => 'Depeche Mode' });
+ my $new_related_cd = $new_artist->new_related('cds', { 'title' => 'Leave in Silence', 'year' => 1982});
+ eval {
+ $new_related_cd->insert;
+ };
+ is ($@, '', 'CD insertion survives by inserting artist');
+ ok($new_artist->in_storage, 'artist inserted');
+ ok($new_related_cd->in_storage, 'new_related_cd inserted');
+}
+
+{
+ my $new_cd = $schema->resultset("CD")->new_result({});
+ my $new_related_artist = $new_cd->new_related('artist', { 'name' => 'Marillion',});
+ lives_ok (
+ sub {
+ $new_related_artist->insert;
+ $new_cd->title( 'Misplaced Childhood' );
+ $new_cd->year ( 1985 );
+ $new_cd->artist( $new_related_artist ); # For exact backward compatibility
+ $new_cd->insert;
+ },
+ 'Reversed staged insertion successful'
+ );
+ ok($new_related_artist->in_storage, 'related artist inserted');
+ ok($new_cd->in_storage, 'cd inserted');
+}
--- /dev/null
+use strict;
+use warnings;
+
+use Test::More;
+use Test::Exception;
+use lib qw(t/lib);
+use DBICTest;
+
+plan tests => 19;
+
+# an insane multicreate
+# (should work, despite the fact that no one will probably use it this way)
+
+my $schema = DBICTest->init_schema();
+
+# first count how many rows do we initially have
+my $counts;
+$counts->{$_} = $schema->resultset($_)->count for qw/Artist CD Genre Producer Tag/;
+
+# do the crazy create
+eval {
+ $schema->resultset('CD')->create ({
+ artist => {
+ name => 'james',
+ },
+ title => 'Greatest hits 1',
+ year => '2012',
+ genre => {
+ name => '"Greatest" collections',
+ },
+ tags => [
+ { tag => 'A' },
+ { tag => 'B' },
+ ],
+ cd_to_producer => [
+ {
+ producer => {
+ name => 'bob',
+ producer_to_cd => [
+ {
+ cd => {
+ artist => {
+ name => 'lars',
+ cds => [
+ {
+ title => 'Greatest hits 2',
+ year => 2012,
+ genre => {
+ name => '"Greatest" collections',
+ },
+ tags => [
+ { tag => 'A' },
+ { tag => 'B' },
+ ],
+ # This cd is created via artist so it doesn't know about producers
+ cd_to_producer => [
+ # if we specify 'bob' here things bomb
+ # as the producer attached to Greatest Hits 1 is
+ # already created, but not yet inserted.
+ # Maybe this can be fixed, but things are hairy
+ # enough already.
+ #
+ #{ producer => { name => 'bob' } },
+ { producer => { name => 'paul' } },
+ { producer => {
+ name => 'flemming',
+ producer_to_cd => [
+ { cd => {
+ artist => {
+ name => 'kirk',
+ cds => [
+ {
+ title => 'Greatest hits 3',
+ year => 2012,
+ genre => {
+ name => '"Greatest" collections',
+ },
+ tags => [
+ { tag => 'A' },
+ { tag => 'B' },
+ ],
+ },
+ {
+ title => 'Greatest hits 4',
+ year => 2012,
+ genre => {
+ name => '"Greatest" collections2',
+ },
+ tags => [
+ { tag => 'A' },
+ { tag => 'B' },
+ ],
+ },
+ ],
+ },
+ title => 'Greatest hits 5',
+ year => 2013,
+ genre => {
+ name => '"Greatest" collections2',
+ },
+ }},
+ ],
+ }},
+ ],
+ },
+ ],
+ },
+ title => 'Greatest hits 6',
+ year => 2012,
+ genre => {
+ name => '"Greatest" collections',
+ },
+ tags => [
+ { tag => 'A' },
+ { tag => 'B' },
+ ],
+ },
+ },
+ {
+ cd => {
+ artist => {
+ name => 'lars', # should already exist
+ # even though the artist 'name' is not uniquely constrained
+ # find_or_create will arguably DWIM
+ },
+ title => 'Greatest hits 7',
+ year => 2013,
+ },
+ },
+ ],
+ },
+ },
+ ],
+ });
+
+ is ($schema->resultset ('Artist')->count, $counts->{Artist} + 3, '3 new artists created');
+ is ($schema->resultset ('Genre')->count, $counts->{Genre} + 2, '2 additional genres created');
+ is ($schema->resultset ('Producer')->count, $counts->{Producer} + 3, '3 new producer');
+ is ($schema->resultset ('CD')->count, $counts->{CD} + 7, '7 new CDs');
+ is ($schema->resultset ('Tag')->count, $counts->{Tag} + 10, '10 new Tags');
+
+ my $cd_rs = $schema->resultset ('CD')
+ ->search ({ title => { -like => 'Greatest hits %' }}, { order_by => 'title'} );
+ is ($cd_rs->count, 7, '7 greatest hits created');
+
+ my $cds_2012 = $cd_rs->search ({ year => 2012});
+ is ($cds_2012->count, 5, '5 CDs created in 2012');
+
+ is (
+ $cds_2012->search(
+ { 'tags.tag' => { -in => [qw/A B/] } },
+ { join => 'tags', group_by => 'me.cdid' }
+ ),
+ 5,
+ 'All 10 tags were pairwise distributed between 5 year-2012 CDs'
+ );
+
+ my $paul_prod = $cd_rs->search (
+ { 'producer.name' => 'paul'},
+ { join => { cd_to_producer => 'producer' } }
+ );
+ is ($paul_prod->count, 1, 'Paul had 1 production');
+ my $pauls_cd = $paul_prod->single;
+ is ($pauls_cd->cd_to_producer->count, 2, 'Paul had one co-producer');
+ is (
+ $pauls_cd->search_related ('cd_to_producer',
+ { 'producer.name' => 'flemming'},
+ { join => 'producer' }
+ )->count,
+ 1,
+ 'The second producer is flemming',
+ );
+
+ my $kirk_cds = $cd_rs->search ({ 'artist.name' => 'kirk' }, { join => 'artist' });
+ is ($kirk_cds, 3, 'Kirk had 3 CDs');
+ is (
+ $kirk_cds->search (
+ { 'cd_to_producer.cd' => { '!=', undef } },
+ { join => 'cd_to_producer' },
+ ),
+ 1,
+ 'Kirk had a producer only on one cd',
+ );
+
+ my $lars_cds = $cd_rs->search ({ 'artist.name' => 'lars' }, { join => 'artist' });
+ is ($lars_cds->count, 3, 'Lars had 3 CDs');
+ is (
+ $lars_cds->search (
+ { 'cd_to_producer.cd' => undef },
+ { join => 'cd_to_producer' },
+ ),
+ 0,
+ 'Lars always had a producer',
+ );
+ is (
+ $lars_cds->search_related ('cd_to_producer',
+ { 'producer.name' => 'flemming'},
+ { join => 'producer' }
+ )->count,
+ 1,
+ 'Lars produced 1 CD with flemming',
+ );
+ is (
+ $lars_cds->search_related ('cd_to_producer',
+ { 'producer.name' => 'bob'},
+ { join => 'producer' }
+ )->count,
+ 2,
+ 'Lars produced 2 CDs with bob',
+ );
+
+ my $bob_prod = $cd_rs->search (
+ { 'producer.name' => 'bob'},
+ { join => { cd_to_producer => 'producer' } }
+ );
+ is ($bob_prod->count, 3, 'Bob produced a total of 3 CDs');
+
+ is (
+ $bob_prod->search ({ 'artist.name' => 'james' }, { join => 'artist' })->count,
+ 1,
+ "Bob produced james' only CD",
+ );
+};
+diag $@ if $@;
+
+1;
ok( ( $ratio < 2 ), 'Overload/bless performance acceptable' )
|| diag(
+ "\n",
"This perl has a substantial slow down when handling large numbers\n",
"of blessed/overloaded objects. This can severely adversely affect\n",
"the performance of DBIx::Class programs. Please read the section\n",
"in the Troubleshooting POD documentation entitled\n",
"'Perl Performance Issues on Red Hat Systems'\n",
+ "As this is an extremely serious condition, the only way to skip\n",
+ "over this test is to --force the installation, or to edit the test\n",
+ "file " . __FILE__ . "\n",
);
# We will only check for the difference in bless handling (whether the
ok( !_possibly_has_bad_overload_performance(),
'Checking whether bless applies to reference not object' )
|| diag(
+ "\n",
"This perl is probably derived from a buggy Red Hat perl build\n",
"Please read the section in the Troubleshooting POD documentation\n",
"entitled 'Perl Performance Issues on Red Hat Systems'\n",
+ "As this is an extremely serious condition, the only way to skip\n",
+ "over this test is to --force the installation, or to edit the test\n",
+ "file " . __FILE__ . "\n",
);
}
plan skip_all => 'Set $ENV{DBICTEST_PG_DSN}, _USER and _PASS to run this test'
unless ($dsn && $dbuser);
-plan tests => 3;
+plan tests => 6;
my $schema = DBICTest::Schema->connection($dsn, $dbuser, $dbpass, { AutoCommit => 1 });
{
local $SIG{__WARN__} = sub {};
- $dbh->do('DROP TABLE IF EXISTS artist');
+ $dbh->do('DROP TABLE IF EXISTS bindtype_test');
+
+ # the blob/clob are for reference only, will be useful when we switch to SQLT and can test Oracle along the way
# the blob/clob are for reference only, will be useful when we switch to SQLT and can test Oracle along the way
$dbh->do(qq[
],{ RaiseError => 1, PrintError => 1 });
}
-# test primary key handling
-my $big_long_string = 'abcd' x 250000;
+# test retrieval of the bytea column
+{
+ my $row = $schema->resultset('BindType')->find({ id => $new->id });
+ is($row->get_column('bytea'), $big_long_string, "Created the blob correctly.");
+}
my $new = $schema->resultset('BindType')->create({ bytea => $big_long_string });
$dbh->do("DROP TABLE bindtype_test");
-
-
+$dbh->do("DROP TABLE bindtype_test");
{ 'DBICTest::Schema' => [qw/
LinerNotes
Artwork
+ Artwork_to_Artist
Image
Lyrics
LyricVersion
{ cascade_copy => 0 } # this would *so* not make sense
);
+__PACKAGE__->has_many(
+ artist_to_artwork => 'DBICTest::Schema::Artwork_to_Artist' => 'artist_id'
+);
+__PACKAGE__->many_to_many('artworks', 'artist_to_artwork', 'artwork');
+
+
sub sqlt_deploy_hook {
my ($self, $sqlt_table) = @_;
-
if ($sqlt_table->schema->translator->producer_type =~ /SQLite$/ ) {
- $sqlt_table->add_index( name => 'artist_name', fields => ['name'] )
+ $sqlt_table->add_index( name => 'artist_name_hookidx', fields => ['name'] )
or die $sqlt_table->error;
}
}
-package # hide from PAUSE
+package # hide from PAUSE
DBICTest::Schema::Artwork;
use base qw/DBIx::Class::Core/;
__PACKAGE__->belongs_to('cd', 'DBICTest::Schema::CD', 'cd_id');
__PACKAGE__->has_many('images', 'DBICTest::Schema::Image', 'artwork_id');
+__PACKAGE__->has_many('artwork_to_artist', 'DBICTest::Schema::Artwork_to_Artist', 'artwork_cd_id');
+__PACKAGE__->many_to_many('artists', 'artwork_to_artist', 'artist');
+
1;
--- /dev/null
+package # hide from PAUSE
+ DBICTest::Schema::Artwork_to_Artist;
+
+use base qw/DBIx::Class::Core/;
+
+__PACKAGE__->table('artwork_to_artist');
+__PACKAGE__->add_columns(
+ 'artwork_cd_id' => {
+ data_type => 'integer',
+ is_foreign_key => 1,
+ },
+ 'artist_id' => {
+ data_type => 'integer',
+ is_foreign_key => 1,
+ },
+);
+__PACKAGE__->set_primary_key(qw/artwork_cd_id artist_id/);
+__PACKAGE__->belongs_to('artwork', 'DBICTest::Schema::Artwork', 'artwork_cd_id');
+__PACKAGE__->belongs_to('artist', 'DBICTest::Schema::Artist', 'artist_id');
+
+1;
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings FATAL => 'all';
+
+use Data::Dumper;
+
+use Test::More;
+
+BEGIN {
+ eval "use SQL::Abstract 1.49";
+ plan $@
+ ? ( skip_all => "Needs SQLA 1.49+" )
+ : ( tests => 4 );
+}
+
+use lib qw(t/lib);
+use DBICTest;
+use DBIC::SqlMakerTest;
+
+my $schema = DBICTest->init_schema();
+my $art_rs = $schema->resultset('Artist');
+my $cdrs = $schema->resultset('CD');
+
+{
+ my $arr = $art_rs->as_query;
+ my ($query, @bind) = @{$$arr};
+
+ is_same_sql_bind(
+ $query, \@bind,
+ "(SELECT me.artistid, me.name, me.rank, me.charfield FROM artist me)", [],
+ );
+}
+
+$art_rs = $art_rs->search({ name => 'Billy Joel' });
+
+{
+ my $arr = $art_rs->as_query;
+ my ($query, @bind) = @{$$arr};
+
+ is_same_sql_bind(
+ $query, \@bind,
+ "(SELECT me.artistid, me.name, me.rank, me.charfield FROM artist me WHERE ( name = ? ))",
+ [ [ name => 'Billy Joel' ] ],
+ );
+}
+
+$art_rs = $art_rs->search({ rank => 2 });
+
+{
+ my $arr = $art_rs->as_query;
+ my ($query, @bind) = @{$$arr};
+
+ is_same_sql_bind(
+ $query, \@bind,
+ "(SELECT me.artistid, me.name, me.rank, me.charfield FROM artist me WHERE ( ( ( rank = ? ) AND ( name = ? ) ) ) )",
+ [ [ rank => 2 ], [ name => 'Billy Joel' ] ],
+ );
+}
+
+my $rscol = $art_rs->get_column( 'charfield' );
+
+{
+ my $arr = $rscol->as_query;
+ my ($query, @bind) = @{$$arr};
+
+ is_same_sql_bind(
+ $query, \@bind,
+ "(SELECT me.charfield FROM artist me WHERE ( ( ( rank = ? ) AND ( name = ? ) ) ) )",
+ [ [ rank => 2 ], [ name => 'Billy Joel' ] ],
+ );
+}
+
+__END__
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings FATAL => 'all';
+
+use Data::Dumper;
+
+use Test::More;
+
+BEGIN {
+ eval "use SQL::Abstract 1.49";
+ plan $@
+ ? ( skip_all => "Needs SQLA 1.49+" )
+ : ( tests => 6 );
+}
+
+use lib qw(t/lib);
+use DBICTest;
+use DBIC::SqlMakerTest;
+
+my $schema = DBICTest->init_schema();
+my $art_rs = $schema->resultset('Artist');
+my $cdrs = $schema->resultset('CD');
+
+{
+ my $cdrs2 = $cdrs->search({
+ artist_id => { 'in' => $art_rs->search({}, { rows => 1 })->get_column( 'id' )->as_query },
+ });
+
+ my $arr = $cdrs2->as_query;
+ my ($query, @bind) = @{$$arr};
+ is_same_sql_bind(
+ $query, \@bind,
+ "SELECT me.cdid,me.artist,me.title,me.year,me.genreid,me.single_track FROM cd me WHERE artist_id IN ( SELECT id FROM artist me LIMIT 1 )",
+ [],
+ );
+}
+
+TODO: {
+ local $TODO = "'+select' doesn't work with as_query yet.";
+ my $rs = $art_rs->search(
+ {},
+ {
+ '+select' => [
+ $cdrs->search({}, { rows => 1 })->get_column('id')->as_query,
+ ],
+ '+as' => [
+ 'cdid',
+ ],
+ },
+ );
+
+ my $arr = $rs->as_query;
+ my ($query, @bind) = @{$$arr};
+ is_same_sql_bind(
+ $query, \@bind,
+ "SELECT me.artistid, me.name, me.rank, me.charfield, (SELECT id FROM cds LIMIT 1) AS cdid FROM artist me",
+ [],
+ );
+}
+
+# simple from
+{
+ my $rs = $cdrs->search(
+ {},
+ {
+ alias => 'cd2',
+ from => [
+ { cd2 => $cdrs->search({ id => { '>' => 20 } })->as_query },
+ ],
+ },
+ );
+
+ my $arr = $rs->as_query;
+ my ($query, @bind) = @{$$arr};
+ is_same_sql_bind(
+ $query, \@bind,
+ "SELECT cd2.cdid, cd2.artist, cd2.title, cd2.year, cd2.genreid, cd2.single_track FROM (SELECT me.cdid,me.artist,me.title,me.year,me.genreid,me.single_track FROM cd me WHERE id > 20) cd2",
+ [],
+ );
+}
+
+# nested from
+{
+ my $art_rs2 = $schema->resultset('Artist')->search({},
+ {
+ from => [ { 'me' => 'artist' },
+ [ { 'cds' => $cdrs->search({},{ 'select' => [\'me.artist as cds_artist' ]})->as_query },
+ { 'me.artistid' => 'cds_artist' } ] ]
+ });
+
+ my $arr = $art_rs2->as_query;
+ my ($query, @bind) = @{$$arr};
+ is_same_sql_bind(
+ $query, \@bind,
+ "SELECT me.artistid, me.name, me.rank, me.charfield FROM artist me JOIN (SELECT me.artist as cds_artist FROM cd me) cds ON me.artistid = cds_artist", []
+ );
+
+
+}
+
+# nested subquery in from
+{
+ my $rs = $cdrs->search(
+ {},
+ {
+ alias => 'cd2',
+ from => [
+ { cd2 => $cdrs->search(
+ { id => { '>' => 20 } },
+ {
+ alias => 'cd3',
+ from => [
+ { cd3 => $cdrs->search( { id => { '<' => 40 } } )->as_query }
+ ],
+ }, )->as_query },
+ ],
+ },
+ );
+
+ my $arr = $rs->as_query;
+ my ($query, @bind) = @{$$arr};
+ is_same_sql_bind(
+ $query, \@bind,
+ "SELECT cd2.cdid, cd2.artist, cd2.title, cd2.year, cd2.genreid, cd2.single_track
+ FROM
+ (SELECT cd3.cdid,cd3.artist,cd3.title,cd3.year,cd3.genreid,cd3.single_track
+ FROM
+ (SELECT me.cdid,me.artist,me.title,me.year,me.genreid,me.single_track
+ FROM cd me WHERE id < 40) cd3
+ WHERE id > 20) cd2",
+ [],
+ );
+
+}
+
+{
+ my $rs = $cdrs->search({
+ year => {
+ '=' => $cdrs->search(
+ { artistid => { '=' => \'me.artistid' } },
+ { alias => 'inner' }
+ )->get_column('year')->max_rs->as_query,
+ },
+ });
+ my $arr = $rs->as_query;
+ my ($query, @bind) = @{$$arr};
+ is_same_sql_bind(
+ $query, \@bind,
+ "SELECT me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track FROM cd me WHERE year = (SELECT MAX(inner.year) FROM cd inner WHERE artistid = me.artistid)",
+ [],
+ );
+}
+
+__END__