| Filename | /usr/share/perl5/DBIx/Class/ResultSet.pm |
| Statements | Executed 1230032 statements in 2.51s |
| Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
|---|---|---|---|---|---|
| 9000 | 3 | 1 | 1.17s | 1.37s | DBIx::Class::ResultSet::_resolved_attrs |
| 3000 | 1 | 1 | 205ms | 445ms | DBIx::Class::ResultSet::search_rs |
| 3000 | 1 | 1 | 169ms | 234ms | DBIx::Class::ResultSet::_construct_results |
| 6000 | 2 | 2 | 165ms | 483ms | DBIx::Class::ResultSet::new |
| 3000 | 1 | 1 | 122ms | 8.23s | DBIx::Class::ResultSet::single |
| 9000 | 2 | 1 | 119ms | 119ms | DBIx::Class::ResultSet::_normalize_selection |
| 9000 | 2 | 1 | 97.0ms | 150ms | DBIx::Class::ResultSet::result_class |
| 186000 | 5 | 1 | 42.6ms | 42.6ms | DBIx::Class::ResultSet::CORE:match (opcode) |
| 3000 | 1 | 1 | 41.3ms | 46.1ms | DBIx::Class::ResultSet::_remove_alias |
| 3000 | 1 | 1 | 36.3ms | 82.5ms | DBIx::Class::ResultSet::_merge_with_rscond |
| 60000 | 1 | 1 | 33.3ms | 33.3ms | DBIx::Class::ResultSet::CORE:regcomp (opcode) |
| 3000 | 1 | 1 | 31.9ms | 34.5ms | DBIx::Class::ResultSet::_qualify_cond_columns |
| 3000 | 1 | 1 | 22.2ms | 22.2ms | DBIx::Class::ResultSet::_stack_cond |
| 3000 | 1 | 1 | 18.4ms | 102ms | DBIx::Class::ResultSet::_merge_attr |
| 3000 | 1 | 1 | 15.8ms | 460ms | DBIx::Class::ResultSet::search |
| 6000 | 1 | 1 | 9.94ms | 9.94ms | DBIx::Class::ResultSet::get_cache |
| 3000 | 1 | 1 | 7.89ms | 7.89ms | DBIx::Class::ResultSet::__ANON__[:3926] |
| 1 | 1 | 1 | 1.50ms | 1.92ms | DBIx::Class::ResultSet::BEGIN@7 |
| 1 | 1 | 1 | 12µs | 18µs | DBIx::Class::ResultSet::BEGIN@3 |
| 1 | 1 | 1 | 12µs | 43µs | DBIx::Class::ResultSet::BEGIN@8 |
| 1 | 1 | 1 | 11µs | 55µs | DBIx::Class::ResultSet::BEGIN@26 |
| 1 | 1 | 1 | 10µs | 14µs | DBIx::Class::ResultSet::BEGIN@4 |
| 1 | 1 | 1 | 9µs | 178µs | DBIx::Class::ResultSet::BEGIN@23 |
| 1 | 1 | 1 | 9µs | 55µs | DBIx::Class::ResultSet::BEGIN@5 |
| 1 | 1 | 1 | 9µs | 30µs | DBIx::Class::ResultSet::BEGIN@12 |
| 1 | 1 | 1 | 8µs | 52µs | DBIx::Class::ResultSet::BEGIN@6 |
| 1 | 1 | 1 | 8µs | 28µs | DBIx::Class::ResultSet::BEGIN@9 |
| 1 | 1 | 1 | 4µs | 4µs | DBIx::Class::ResultSet::BEGIN@15 |
| 1 | 1 | 1 | 4µs | 4µs | DBIx::Class::ResultSet::BEGIN@17 |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::STORABLE_freeze |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::STORABLE_thaw |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:1487] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:1909] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:2536] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:3524] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:3712] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:3908] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:3911] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:3913] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:3919] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:3924] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:3933] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:3937] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:3939] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:3947] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:3952] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:3954] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:3961] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:467] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:491] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:889] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::__ANON__[:892] |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::_build_unique_cond |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::_calculate_score |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::_chain_relationship |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::_count_rs |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::_count_subq_rs |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::_has_resolved_attr |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::_merge_joinpref_attr |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::_non_unique_find_fallback |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::_rollout_array |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::_rollout_attr |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::_rollout_hash |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::_rs_update_delete |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::all |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::as_query |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::as_subselect_rs |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::clear_cache |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::count |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::count_literal |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::count_rs |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::create |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::current_source_alias |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::cursor |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::delete |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::delete_all |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::find |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::find_or_create |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::find_or_new |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::first |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::get_column |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::is_ordered |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::is_paged |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::new_result |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::next |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::page |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::pager |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::populate |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::related_resultset |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::reset |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::search_like |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::search_literal |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::search_related |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::search_related_rs |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::set_cache |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::slice |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::throw_exception |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::update |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::update_all |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::update_or_create |
| 0 | 0 | 0 | 0s | 0s | DBIx::Class::ResultSet::update_or_new |
| Line | State ments |
Time on line |
Calls | Time in subs |
Code |
|---|---|---|---|---|---|
| 1 | package DBIx::Class::ResultSet; | ||||
| 2 | |||||
| 3 | 2 | 32µs | 2 | 24µs | # spent 18µs (12+6) within DBIx::Class::ResultSet::BEGIN@3 which was called:
# once (12µs+6µs) by DBIx::Class::ResultSource::Table::BEGIN@6 at line 3 # spent 18µs making 1 call to DBIx::Class::ResultSet::BEGIN@3
# spent 6µs making 1 call to strict::import |
| 4 | 2 | 35µs | 2 | 17µs | # spent 14µs (10+4) within DBIx::Class::ResultSet::BEGIN@4 which was called:
# once (10µs+4µs) by DBIx::Class::ResultSource::Table::BEGIN@6 at line 4 # spent 14µs making 1 call to DBIx::Class::ResultSet::BEGIN@4
# spent 4µs making 1 call to warnings::import |
| 5 | 2 | 74µs | 2 | 55µs | # spent 55µs (9+46) within DBIx::Class::ResultSet::BEGIN@5 which was called:
# once (9µs+46µs) by DBIx::Class::ResultSource::Table::BEGIN@6 at line 5 # spent 55µs making 1 call to DBIx::Class::ResultSet::BEGIN@5
# spent 46µs making 1 call to base::import, recursion: max depth 1, sum of overlapping time 46µs |
| 6 | 2 | 24µs | 2 | 95µs | # spent 52µs (8+43) within DBIx::Class::ResultSet::BEGIN@6 which was called:
# once (8µs+43µs) by DBIx::Class::ResultSource::Table::BEGIN@6 at line 6 # spent 52µs making 1 call to DBIx::Class::ResultSet::BEGIN@6
# spent 43µs making 1 call to DBIx::Class::Carp::import |
| 7 | 2 | 160µs | 1 | 1.92ms | # spent 1.92ms (1.50+416µs) within DBIx::Class::ResultSet::BEGIN@7 which was called:
# once (1.50ms+416µs) by DBIx::Class::ResultSource::Table::BEGIN@6 at line 7 # spent 1.92ms making 1 call to DBIx::Class::ResultSet::BEGIN@7 |
| 8 | 2 | 67µs | 2 | 73µs | # spent 43µs (12+31) within DBIx::Class::ResultSet::BEGIN@8 which was called:
# once (12µs+31µs) by DBIx::Class::ResultSource::Table::BEGIN@6 at line 8 # spent 43µs making 1 call to DBIx::Class::ResultSet::BEGIN@8
# spent 31µs making 1 call to Exporter::import |
| 9 | 1 | 27µs | 1 | 20µs | # spent 28µs (8+20) within DBIx::Class::ResultSet::BEGIN@9 which was called:
# once (8µs+20µs) by DBIx::Class::ResultSource::Table::BEGIN@6 at line 11 # spent 20µs making 1 call to Exporter::import |
| 10 | fail_on_internal_wantarray fail_on_internal_call UNRESOLVABLE_CONDITION | ||||
| 11 | 1 | 19µs | 1 | 28µs | ); # spent 28µs making 1 call to DBIx::Class::ResultSet::BEGIN@9 |
| 12 | 2 | 49µs | 2 | 52µs | # spent 30µs (9+22) within DBIx::Class::ResultSet::BEGIN@12 which was called:
# once (9µs+22µs) by DBIx::Class::ResultSource::Table::BEGIN@6 at line 12 # spent 30µs making 1 call to DBIx::Class::ResultSet::BEGIN@12
# spent 22µs making 1 call to Exporter::import |
| 13 | |||||
| 14 | # not importing first() as it will clash with our own method | ||||
| 15 | 2 | 43µs | 1 | 4µs | # spent 4µs within DBIx::Class::ResultSet::BEGIN@15 which was called:
# once (4µs+0s) by DBIx::Class::ResultSource::Table::BEGIN@6 at line 15 # spent 4µs making 1 call to DBIx::Class::ResultSet::BEGIN@15 |
| 16 | |||||
| 17 | # spent 4µs within DBIx::Class::ResultSet::BEGIN@17 which was called:
# once (4µs+0s) by DBIx::Class::ResultSource::Table::BEGIN@6 at line 21 | ||||
| 18 | # De-duplication in _merge_attr() is disabled, but left in for reference | ||||
| 19 | # (the merger is used for other things that ought not to be de-duped) | ||||
| 20 | 1 | 4µs | *__HM_DEDUP = sub () { 0 }; | ||
| 21 | 1 | 18µs | 1 | 4µs | } # spent 4µs making 1 call to DBIx::Class::ResultSet::BEGIN@17 |
| 22 | |||||
| 23 | 2 | 208µs | 2 | 347µs | # spent 178µs (9+169) within DBIx::Class::ResultSet::BEGIN@23 which was called:
# once (9µs+169µs) by DBIx::Class::ResultSource::Table::BEGIN@6 at line 23 # spent 178µs making 1 call to DBIx::Class::ResultSet::BEGIN@23
# spent 169µs making 1 call to namespace::clean::import |
| 24 | |||||
| 25 | use overload | ||||
| 26 | 1 | 58µs | 1 | 44µs | # spent 55µs (11+44) within DBIx::Class::ResultSet::BEGIN@26 which was called:
# once (11µs+44µs) by DBIx::Class::ResultSource::Table::BEGIN@6 at line 28 # spent 44µs making 1 call to overload::import |
| 27 | 'bool' => "_bool", | ||||
| 28 | 1 | 13.3ms | 1 | 55µs | fallback => 1; # spent 55µs making 1 call to DBIx::Class::ResultSet::BEGIN@26 |
| 29 | |||||
| 30 | # this is real - CDBICompat overrides it with insanity | ||||
| 31 | # yes, prototype won't matter, but that's for now ;) | ||||
| 32 | sub _bool () { 1 } | ||||
| 33 | |||||
| 34 | 1 | 18µs | 1 | 446µs | __PACKAGE__->mk_group_accessors('simple' => qw/_result_class result_source/); # spent 446µs making 1 call to Class::Accessor::Grouped::mk_group_accessors |
| 35 | |||||
| 36 | =head1 NAME | ||||
| 37 | |||||
| 38 | DBIx::Class::ResultSet - Represents a query used for fetching a set of results. | ||||
| 39 | |||||
| 40 | =head1 SYNOPSIS | ||||
| 41 | |||||
| 42 | my $users_rs = $schema->resultset('User'); | ||||
| 43 | while( $user = $users_rs->next) { | ||||
| 44 | print $user->username; | ||||
| 45 | } | ||||
| 46 | |||||
| 47 | my $registered_users_rs = $schema->resultset('User')->search({ registered => 1 }); | ||||
| 48 | my @cds_in_2005 = $schema->resultset('CD')->search({ year => 2005 })->all(); | ||||
| 49 | |||||
| 50 | =head1 DESCRIPTION | ||||
| 51 | |||||
| 52 | A ResultSet is an object which stores a set of conditions representing | ||||
| 53 | a query. It is the backbone of DBIx::Class (i.e. the really | ||||
| 54 | important/useful bit). | ||||
| 55 | |||||
| 56 | No SQL is executed on the database when a ResultSet is created, it | ||||
| 57 | just stores all the conditions needed to create the query. | ||||
| 58 | |||||
| 59 | A basic ResultSet representing the data of an entire table is returned | ||||
| 60 | by calling C<resultset> on a L<DBIx::Class::Schema> and passing in a | ||||
| 61 | L<Source|DBIx::Class::Manual::Glossary/ResultSource> name. | ||||
| 62 | |||||
| 63 | my $users_rs = $schema->resultset('User'); | ||||
| 64 | |||||
| 65 | A new ResultSet is returned from calling L</search> on an existing | ||||
| 66 | ResultSet. The new one will contain all the conditions of the | ||||
| 67 | original, plus any new conditions added in the C<search> call. | ||||
| 68 | |||||
| 69 | A ResultSet also incorporates an implicit iterator. L</next> and L</reset> | ||||
| 70 | can be used to walk through all the L<DBIx::Class::Row>s the ResultSet | ||||
| 71 | represents. | ||||
| 72 | |||||
| 73 | The query that the ResultSet represents is B<only> executed against | ||||
| 74 | the database when these methods are called: | ||||
| 75 | L</find>, L</next>, L</all>, L</first>, L</single>, L</count>. | ||||
| 76 | |||||
| 77 | If a resultset is used in a numeric context it returns the L</count>. | ||||
| 78 | However, if it is used in a boolean context it is B<always> true. So if | ||||
| 79 | you want to check if a resultset has any results, you must use C<if $rs | ||||
| 80 | != 0>. | ||||
| 81 | |||||
| 82 | =head1 EXAMPLES | ||||
| 83 | |||||
| 84 | =head2 Chaining resultsets | ||||
| 85 | |||||
| 86 | Let's say you've got a query that needs to be run to return some data | ||||
| 87 | to the user. But, you have an authorization system in place that | ||||
| 88 | prevents certain users from seeing certain information. So, you want | ||||
| 89 | to construct the basic query in one method, but add constraints to it in | ||||
| 90 | another. | ||||
| 91 | |||||
| 92 | sub get_data { | ||||
| 93 | my $self = shift; | ||||
| 94 | my $request = $self->get_request; # Get a request object somehow. | ||||
| 95 | my $schema = $self->result_source->schema; | ||||
| 96 | |||||
| 97 | my $cd_rs = $schema->resultset('CD')->search({ | ||||
| 98 | title => $request->param('title'), | ||||
| 99 | year => $request->param('year'), | ||||
| 100 | }); | ||||
| 101 | |||||
| 102 | $cd_rs = $self->apply_security_policy( $cd_rs ); | ||||
| 103 | |||||
| 104 | return $cd_rs->all(); | ||||
| 105 | } | ||||
| 106 | |||||
| 107 | sub apply_security_policy { | ||||
| 108 | my $self = shift; | ||||
| 109 | my ($rs) = @_; | ||||
| 110 | |||||
| 111 | return $rs->search({ | ||||
| 112 | subversive => 0, | ||||
| 113 | }); | ||||
| 114 | } | ||||
| 115 | |||||
| 116 | =head3 Resolving conditions and attributes | ||||
| 117 | |||||
| 118 | When a resultset is chained from another resultset (e.g.: | ||||
| 119 | C<< my $new_rs = $old_rs->search(\%extra_cond, \%attrs) >>), conditions | ||||
| 120 | and attributes with the same keys need resolving. | ||||
| 121 | |||||
| 122 | If any of L</columns>, L</select>, L</as> are present, they reset the | ||||
| 123 | original selection, and start the selection "clean". | ||||
| 124 | |||||
| 125 | The L</join>, L</prefetch>, L</+columns>, L</+select>, L</+as> attributes | ||||
| 126 | are merged into the existing ones from the original resultset. | ||||
| 127 | |||||
| 128 | The L</where> and L</having> attributes, and any search conditions, are | ||||
| 129 | merged with an SQL C<AND> to the existing condition from the original | ||||
| 130 | resultset. | ||||
| 131 | |||||
| 132 | All other attributes are overridden by any new ones supplied in the | ||||
| 133 | search attributes. | ||||
| 134 | |||||
| 135 | =head2 Multiple queries | ||||
| 136 | |||||
| 137 | Since a resultset just defines a query, you can do all sorts of | ||||
| 138 | things with it with the same object. | ||||
| 139 | |||||
| 140 | # Don't hit the DB yet. | ||||
| 141 | my $cd_rs = $schema->resultset('CD')->search({ | ||||
| 142 | title => 'something', | ||||
| 143 | year => 2009, | ||||
| 144 | }); | ||||
| 145 | |||||
| 146 | # Each of these hits the DB individually. | ||||
| 147 | my $count = $cd_rs->count; | ||||
| 148 | my $most_recent = $cd_rs->get_column('date_released')->max(); | ||||
| 149 | my @records = $cd_rs->all; | ||||
| 150 | |||||
| 151 | And it's not just limited to SELECT statements. | ||||
| 152 | |||||
| 153 | $cd_rs->delete(); | ||||
| 154 | |||||
| 155 | This is even cooler: | ||||
| 156 | |||||
| 157 | $cd_rs->create({ artist => 'Fred' }); | ||||
| 158 | |||||
| 159 | Which is the same as: | ||||
| 160 | |||||
| 161 | $schema->resultset('CD')->create({ | ||||
| 162 | title => 'something', | ||||
| 163 | year => 2009, | ||||
| 164 | artist => 'Fred' | ||||
| 165 | }); | ||||
| 166 | |||||
| 167 | See: L</search>, L</count>, L</get_column>, L</all>, L</create>. | ||||
| 168 | |||||
| 169 | =head2 Custom ResultSet classes | ||||
| 170 | |||||
| 171 | To add methods to your resultsets, you can subclass L<DBIx::Class::ResultSet>, similar to: | ||||
| 172 | |||||
| 173 | package MyApp::Schema::ResultSet::User; | ||||
| 174 | |||||
| 175 | use strict; | ||||
| 176 | use warnings; | ||||
| 177 | |||||
| 178 | use base 'DBIx::Class::ResultSet'; | ||||
| 179 | |||||
| 180 | sub active { | ||||
| 181 | my $self = shift; | ||||
| 182 | $self->search({ $self->current_source_alias . '.active' => 1 }); | ||||
| 183 | } | ||||
| 184 | |||||
| 185 | sub unverified { | ||||
| 186 | my $self = shift; | ||||
| 187 | $self->search({ $self->current_source_alias . '.verified' => 0 }); | ||||
| 188 | } | ||||
| 189 | |||||
| 190 | sub created_n_days_ago { | ||||
| 191 | my ($self, $days_ago) = @_; | ||||
| 192 | $self->search({ | ||||
| 193 | $self->current_source_alias . '.create_date' => { | ||||
| 194 | '<=', | ||||
| 195 | $self->result_source->schema->storage->datetime_parser->format_datetime( | ||||
| 196 | DateTime->now( time_zone => 'UTC' )->subtract( days => $days_ago ) | ||||
| 197 | )} | ||||
| 198 | }); | ||||
| 199 | } | ||||
| 200 | |||||
| 201 | sub users_to_warn { shift->active->unverified->created_n_days_ago(7) } | ||||
| 202 | |||||
| 203 | 1; | ||||
| 204 | |||||
| 205 | See L<DBIx::Class::Schema/load_namespaces> on how DBIC can discover and | ||||
| 206 | automatically attach L<Result|DBIx::Class::Manual::ResultClass>-specific | ||||
| 207 | L<ResulSet|DBIx::Class::ResultSet> classes. | ||||
| 208 | |||||
| 209 | =head3 ResultSet subclassing with Moose and similar constructor-providers | ||||
| 210 | |||||
| 211 | Using L<Moose> or L<Moo> in your ResultSet classes is usually overkill, but | ||||
| 212 | you may find it useful if your ResultSets contain a lot of business logic | ||||
| 213 | (e.g. C<has xml_parser>, C<has json>, etc) or if you just prefer to organize | ||||
| 214 | your code via roles. | ||||
| 215 | |||||
| 216 | In order to write custom ResultSet classes with L<Moo> you need to use the | ||||
| 217 | following template. The L<BUILDARGS|Moo/BUILDARGS> is necessary due to the | ||||
| 218 | unusual signature of the L<constructor provided by DBIC | ||||
| 219 | |DBIx::Class::ResultSet/new> C<< ->new($source, \%args) >>. | ||||
| 220 | |||||
| 221 | use Moo; | ||||
| 222 | extends 'DBIx::Class::ResultSet'; | ||||
| 223 | sub BUILDARGS { $_[2] } # ::RS::new() expects my ($class, $rsrc, $args) = @_ | ||||
| 224 | |||||
| 225 | ...your code... | ||||
| 226 | |||||
| 227 | 1; | ||||
| 228 | |||||
| 229 | If you want to build your custom ResultSet classes with L<Moose>, you need | ||||
| 230 | a similar, though a little more elaborate template in order to interface the | ||||
| 231 | inlining of the L<Moose>-provided | ||||
| 232 | L<object constructor|Moose::Manual::Construction/WHERE'S THE CONSTRUCTOR?>, | ||||
| 233 | with the DBIC one. | ||||
| 234 | |||||
| 235 | package MyApp::Schema::ResultSet::User; | ||||
| 236 | |||||
| 237 | use Moose; | ||||
| 238 | use MooseX::NonMoose; | ||||
| 239 | extends 'DBIx::Class::ResultSet'; | ||||
| 240 | |||||
| 241 | sub BUILDARGS { $_[2] } # ::RS::new() expects my ($class, $rsrc, $args) = @_ | ||||
| 242 | |||||
| 243 | ...your code... | ||||
| 244 | |||||
| 245 | __PACKAGE__->meta->make_immutable; | ||||
| 246 | |||||
| 247 | 1; | ||||
| 248 | |||||
| 249 | The L<MooseX::NonMoose> is necessary so that the L<Moose> constructor does not | ||||
| 250 | entirely overwrite the DBIC one (in contrast L<Moo> does this automatically). | ||||
| 251 | Alternatively, you can skip L<MooseX::NonMoose> and get by with just L<Moose> | ||||
| 252 | instead by doing: | ||||
| 253 | |||||
| 254 | __PACKAGE__->meta->make_immutable(inline_constructor => 0); | ||||
| 255 | |||||
| 256 | =head1 METHODS | ||||
| 257 | |||||
| 258 | =head2 new | ||||
| 259 | |||||
| 260 | =over 4 | ||||
| 261 | |||||
| 262 | =item Arguments: L<$source|DBIx::Class::ResultSource>, L<\%attrs?|/ATTRIBUTES> | ||||
| 263 | |||||
| 264 | =item Return Value: L<$resultset|/search> | ||||
| 265 | |||||
| 266 | =back | ||||
| 267 | |||||
| 268 | The resultset constructor. Takes a source object (usually a | ||||
| 269 | L<DBIx::Class::ResultSourceProxy::Table>) and an attribute hash (see | ||||
| 270 | L</ATTRIBUTES> below). Does not perform any queries -- these are | ||||
| 271 | executed as needed by the other methods. | ||||
| 272 | |||||
| 273 | Generally you never construct a resultset manually. Instead you get one | ||||
| 274 | from e.g. a | ||||
| 275 | C<< $schema->L<resultset|DBIx::Class::Schema/resultset>('$source_name') >> | ||||
| 276 | or C<< $another_resultset->L<search|/search>(...) >> (the later called in | ||||
| 277 | scalar context): | ||||
| 278 | |||||
| 279 | my $rs = $schema->resultset('CD')->search({ title => '100th Window' }); | ||||
| 280 | |||||
| 281 | =over | ||||
| 282 | |||||
| 283 | =item WARNING | ||||
| 284 | |||||
| 285 | If called on an object, proxies to L</new_result> instead, so | ||||
| 286 | |||||
| 287 | my $cd = $schema->resultset('CD')->new({ title => 'Spoon' }); | ||||
| 288 | |||||
| 289 | will return a CD object, not a ResultSet, and is equivalent to: | ||||
| 290 | |||||
| 291 | my $cd = $schema->resultset('CD')->new_result({ title => 'Spoon' }); | ||||
| 292 | |||||
| 293 | Please also keep in mind that many internals call L</new_result> directly, | ||||
| 294 | so overloading this method with the idea of intercepting new result object | ||||
| 295 | creation B<will not work>. See also warning pertaining to L</create>. | ||||
| 296 | |||||
| 297 | =back | ||||
| 298 | |||||
| 299 | =cut | ||||
| 300 | |||||
| 301 | # spent 483ms (165+318) within DBIx::Class::ResultSet::new which was called 6000 times, avg 80µs/call:
# 3000 times (101ms+211ms) by DBIx::Class::ResultSource::resultset at line 1129 of DBIx/Class/ResultSource.pm, avg 104µs/call
# 3000 times (63.3ms+107ms) by DBIx::Class::ResultSet::search_rs at line 548, avg 57µs/call | ||||
| 302 | 6000 | 2.41ms | my $class = shift; | ||
| 303 | |||||
| 304 | 6000 | 1.50ms | if (ref $class) { | ||
| 305 | DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call; | ||||
| 306 | return $class->new_result(@_); | ||||
| 307 | } | ||||
| 308 | |||||
| 309 | 6000 | 2.59ms | my ($source, $attrs) = @_; | ||
| 310 | 6000 | 59.2ms | 6000 | 26.3ms | $source = $source->resolve # spent 26.3ms making 6000 calls to UNIVERSAL::isa, avg 4µs/call |
| 311 | if $source->isa('DBIx::Class::ResultSourceHandle'); | ||||
| 312 | |||||
| 313 | 6000 | 10.2ms | $attrs = { %{$attrs||{}} }; | ||
| 314 | 6000 | 3.93ms | delete @{$attrs}{qw(_last_sqlmaker_alias_map _simple_passthrough_construction)}; | ||
| 315 | |||||
| 316 | 6000 | 2.92ms | if ($attrs->{page}) { | ||
| 317 | $attrs->{rows} ||= 10; | ||||
| 318 | } | ||||
| 319 | |||||
| 320 | 6000 | 3.64ms | $attrs->{alias} ||= 'me'; | ||
| 321 | |||||
| 322 | 6000 | 17.7ms | my $self = bless { | ||
| 323 | result_source => $source, | ||||
| 324 | cond => $attrs->{where}, | ||||
| 325 | pager => undef, | ||||
| 326 | attrs => $attrs, | ||||
| 327 | }, $class; | ||||
| 328 | |||||
| 329 | # if there is a dark selector, this means we are already in a | ||||
| 330 | # chain and the cleanup/sanification was taken care of by | ||||
| 331 | # _search_rs already | ||||
| 332 | 6000 | 11.5ms | 6000 | 84.7ms | $self->_normalize_selection($attrs) # spent 84.7ms making 6000 calls to DBIx::Class::ResultSet::_normalize_selection, avg 14µs/call |
| 333 | unless $attrs->{_dark_selector}; | ||||
| 334 | |||||
| 335 | 6000 | 17.8ms | 9000 | 207ms | $self->result_class( # spent 143ms making 6000 calls to DBIx::Class::ResultSet::result_class, avg 24µs/call
# spent 64.5ms making 3000 calls to DBIx::Class::ResultSource::result_class, avg 21µs/call |
| 336 | $attrs->{result_class} || $source->result_class | ||||
| 337 | ); | ||||
| 338 | |||||
| 339 | 6000 | 19.4ms | $self; | ||
| 340 | } | ||||
| 341 | |||||
| 342 | =head2 search | ||||
| 343 | |||||
| 344 | =over 4 | ||||
| 345 | |||||
| 346 | =item Arguments: L<$cond|DBIx::Class::SQLMaker> | undef, L<\%attrs?|/ATTRIBUTES> | ||||
| 347 | |||||
| 348 | =item Return Value: $resultset (scalar context) | L<@result_objs|DBIx::Class::Manual::ResultClass> (list context) | ||||
| 349 | |||||
| 350 | =back | ||||
| 351 | |||||
| 352 | my @cds = $cd_rs->search({ year => 2001 }); # "... WHERE year = 2001" | ||||
| 353 | my $new_rs = $cd_rs->search({ year => 2005 }); | ||||
| 354 | |||||
| 355 | my $new_rs = $cd_rs->search([ { year => 2005 }, { year => 2004 } ]); | ||||
| 356 | # year = 2005 OR year = 2004 | ||||
| 357 | |||||
| 358 | In list context, C<< ->all() >> is called implicitly on the resultset, thus | ||||
| 359 | returning a list of L<result|DBIx::Class::Manual::ResultClass> objects instead. | ||||
| 360 | To avoid that, use L</search_rs>. | ||||
| 361 | |||||
| 362 | If you need to pass in additional attributes but no additional condition, | ||||
| 363 | call it as C<search(undef, \%attrs)>. | ||||
| 364 | |||||
| 365 | # "SELECT name, artistid FROM $artist_table" | ||||
| 366 | my @all_artists = $schema->resultset('Artist')->search(undef, { | ||||
| 367 | columns => [qw/name artistid/], | ||||
| 368 | }); | ||||
| 369 | |||||
| 370 | For a list of attributes that can be passed to C<search>, see | ||||
| 371 | L</ATTRIBUTES>. For more examples of using this function, see | ||||
| 372 | L<Searching|DBIx::Class::Manual::Cookbook/SEARCHING>. For a complete | ||||
| 373 | documentation for the first argument, see L<SQL::Abstract/"WHERE CLAUSES"> | ||||
| 374 | and its extension L<DBIx::Class::SQLMaker>. | ||||
| 375 | |||||
| 376 | For more help on using joins with search, see L<DBIx::Class::Manual::Joining>. | ||||
| 377 | |||||
| 378 | =head3 CAVEAT | ||||
| 379 | |||||
| 380 | Note that L</search> does not process/deflate any of the values passed in the | ||||
| 381 | L<SQL::Abstract>-compatible search condition structure. This is unlike other | ||||
| 382 | condition-bound methods L</new_result>, L</create> and L</find>. The user must ensure | ||||
| 383 | manually that any value passed to this method will stringify to something the | ||||
| 384 | RDBMS knows how to deal with. A notable example is the handling of L<DateTime> | ||||
| 385 | objects, for more info see: | ||||
| 386 | L<DBIx::Class::Manual::Cookbook/Formatting DateTime objects in queries>. | ||||
| 387 | |||||
| 388 | =cut | ||||
| 389 | |||||
| 390 | # spent 460ms (15.8+445) within DBIx::Class::ResultSet::search which was called 3000 times, avg 153µs/call:
# 3000 times (15.8ms+445ms) by Koha::Objects::find at line 903, avg 153µs/call | ||||
| 391 | 3000 | 1.15ms | my $self = shift; | ||
| 392 | 3000 | 6.75ms | 3000 | 445ms | my $rs = $self->search_rs( @_ ); # spent 445ms making 3000 calls to DBIx::Class::ResultSet::search_rs, avg 148µs/call |
| 393 | |||||
| 394 | 3000 | 8.95ms | if (wantarray) { | ||
| 395 | DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_WANTARRAY and my $sog = fail_on_internal_wantarray; | ||||
| 396 | return $rs->all; | ||||
| 397 | } | ||||
| 398 | elsif (defined wantarray) { | ||||
| 399 | return $rs; | ||||
| 400 | } | ||||
| 401 | else { | ||||
| 402 | # we can be called by a relationship helper, which in | ||||
| 403 | # turn may be called in void context due to some braindead | ||||
| 404 | # overload or whatever else the user decided to be clever | ||||
| 405 | # at this particular day. Thus limit the exception to | ||||
| 406 | # external code calls only | ||||
| 407 | $self->throw_exception ('->search is *not* a mutator, calling it in void context makes no sense') | ||||
| 408 | if (caller)[0] !~ /^\QDBIx::Class::/; | ||||
| 409 | |||||
| 410 | return (); | ||||
| 411 | } | ||||
| 412 | } | ||||
| 413 | |||||
| 414 | =head2 search_rs | ||||
| 415 | |||||
| 416 | =over 4 | ||||
| 417 | |||||
| 418 | =item Arguments: L<$cond|DBIx::Class::SQLMaker>, L<\%attrs?|/ATTRIBUTES> | ||||
| 419 | |||||
| 420 | =item Return Value: L<$resultset|/search> | ||||
| 421 | |||||
| 422 | =back | ||||
| 423 | |||||
| 424 | This method does the same exact thing as search() except it will | ||||
| 425 | always return a resultset, even in list context. | ||||
| 426 | |||||
| 427 | =cut | ||||
| 428 | |||||
| 429 | # spent 445ms (205+240) within DBIx::Class::ResultSet::search_rs which was called 3000 times, avg 148µs/call:
# 3000 times (205ms+240ms) by DBIx::Class::ResultSet::search at line 392, avg 148µs/call | ||||
| 430 | 3000 | 771µs | my $self = shift; | ||
| 431 | |||||
| 432 | 3000 | 2.32ms | 1 | 900ns | my $rsrc = $self->result_source; # spent 900ns making 1 call to DBIx::Class::ResultSet::result_source |
| 433 | 3000 | 1.48ms | my ($call_cond, $call_attrs); | ||
| 434 | |||||
| 435 | # Special-case handling for (undef, undef) or (undef) | ||||
| 436 | # Note that (foo => undef) is valid deprecated syntax | ||||
| 437 | 3000 | 4.53ms | @_ = () if not scalar grep { defined $_ } @_; | ||
| 438 | |||||
| 439 | # just a cond | ||||
| 440 | 3000 | 7.71ms | if (@_ == 1) { | ||
| 441 | $call_cond = shift; | ||||
| 442 | } | ||||
| 443 | # fish out attrs in the ($condref, $attr) case | ||||
| 444 | elsif (@_ == 2 and ( ! defined $_[0] or (ref $_[0]) ne '') ) { | ||||
| 445 | ($call_cond, $call_attrs) = @_; | ||||
| 446 | } | ||||
| 447 | elsif (@_ % 2) { | ||||
| 448 | $self->throw_exception('Odd number of arguments to search') | ||||
| 449 | } | ||||
| 450 | # legacy search | ||||
| 451 | elsif (@_) { | ||||
| 452 | carp_unique 'search( %condition ) is deprecated, use search( \%condition ) instead' | ||||
| 453 | unless $rsrc->result_class->isa('DBIx::Class::CDBICompat'); | ||||
| 454 | |||||
| 455 | for my $i (0 .. $#_) { | ||||
| 456 | next if $i % 2; | ||||
| 457 | $self->throw_exception ('All keys in condition key/value pairs must be plain scalars') | ||||
| 458 | if (! defined $_[$i] or ref $_[$i] ne ''); | ||||
| 459 | } | ||||
| 460 | |||||
| 461 | $call_cond = { @_ }; | ||||
| 462 | } | ||||
| 463 | |||||
| 464 | # see if we can keep the cache (no $rs changes) | ||||
| 465 | 3000 | 678µs | my $cache; | ||
| 466 | 3000 | 5.16ms | my %safe = (alias => 1, cache => 1); | ||
| 467 | 6000 | 32.7ms | 3000 | 5.85ms | if ( ! List::Util::first { !$safe{$_} } keys %$call_attrs and ( # spent 5.85ms making 3000 calls to List::Util::first, avg 2µs/call |
| 468 | ! defined $call_cond | ||||
| 469 | or | ||||
| 470 | ref $call_cond eq 'HASH' && ! keys %$call_cond | ||||
| 471 | or | ||||
| 472 | ref $call_cond eq 'ARRAY' && ! @$call_cond | ||||
| 473 | )) { | ||||
| 474 | $cache = $self->get_cache; | ||||
| 475 | } | ||||
| 476 | |||||
| 477 | 3000 | 6.15ms | my $old_attrs = { %{$self->{attrs}} }; | ||
| 478 | 3000 | 3.16ms | my ($old_having, $old_where) = delete @{$old_attrs}{qw(having where)}; | ||
| 479 | |||||
| 480 | 3000 | 3.23ms | my $new_attrs = { %$old_attrs }; | ||
| 481 | |||||
| 482 | # take care of call attrs (only if anything is changing) | ||||
| 483 | 3000 | 3.26ms | if ($call_attrs and keys %$call_attrs) { | ||
| 484 | |||||
| 485 | # copy for _normalize_selection | ||||
| 486 | 3000 | 2.70ms | $call_attrs = { %$call_attrs }; | ||
| 487 | |||||
| 488 | 3000 | 5.16ms | my @selector_attrs = qw/select as columns cols +select +as +columns include_columns/; | ||
| 489 | |||||
| 490 | # reset the current selector list if new selectors are supplied | ||||
| 491 | 15000 | 21.2ms | 3000 | 7.56ms | if (List::Util::first { exists $call_attrs->{$_} } qw/columns cols select as/) { # spent 7.56ms making 3000 calls to List::Util::first, avg 3µs/call |
| 492 | delete @{$old_attrs}{(@selector_attrs, '_dark_selector')}; | ||||
| 493 | } | ||||
| 494 | |||||
| 495 | # Normalize the new selector list (operates on the passed-in attr structure) | ||||
| 496 | # Need to do it on every chain instead of only once on _resolved_attrs, in | ||||
| 497 | # order to allow detection of empty vs partial 'as' | ||||
| 498 | 3000 | 1.45ms | $call_attrs->{_dark_selector} = $old_attrs->{_dark_selector} | ||
| 499 | if $old_attrs->{_dark_selector}; | ||||
| 500 | 3000 | 4.99ms | 3000 | 34.3ms | $self->_normalize_selection ($call_attrs); # spent 34.3ms making 3000 calls to DBIx::Class::ResultSet::_normalize_selection, avg 11µs/call |
| 501 | |||||
| 502 | # start with blind overwriting merge, exclude selector attrs | ||||
| 503 | 3000 | 7.08ms | $new_attrs = { %{$old_attrs}, %{$call_attrs} }; | ||
| 504 | 3000 | 3.16ms | delete @{$new_attrs}{@selector_attrs}; | ||
| 505 | |||||
| 506 | 3000 | 3.07ms | for (@selector_attrs) { | ||
| 507 | 24000 | 15.2ms | $new_attrs->{$_} = $self->_merge_attr($old_attrs->{$_}, $call_attrs->{$_}) | ||
| 508 | if ( exists $old_attrs->{$_} or exists $call_attrs->{$_} ); | ||||
| 509 | } | ||||
| 510 | |||||
| 511 | # older deprecated name, use only if {columns} is not there | ||||
| 512 | 3000 | 2.34ms | if (my $c = delete $new_attrs->{cols}) { | ||
| 513 | carp_unique( "Resultset attribute 'cols' is deprecated, use 'columns' instead" ); | ||||
| 514 | if ($new_attrs->{columns}) { | ||||
| 515 | carp "Resultset specifies both the 'columns' and the legacy 'cols' attributes - ignoring 'cols'"; | ||||
| 516 | } | ||||
| 517 | else { | ||||
| 518 | $new_attrs->{columns} = $c; | ||||
| 519 | } | ||||
| 520 | } | ||||
| 521 | |||||
| 522 | |||||
| 523 | # join/prefetch use their own crazy merging heuristics | ||||
| 524 | 3000 | 2.47ms | foreach my $key (qw/join prefetch/) { | ||
| 525 | 6000 | 3.51ms | $new_attrs->{$key} = $self->_merge_joinpref_attr($old_attrs->{$key}, $call_attrs->{$key}) | ||
| 526 | if exists $call_attrs->{$key}; | ||||
| 527 | } | ||||
| 528 | |||||
| 529 | # stack binds together | ||||
| 530 | 3000 | 6.85ms | $new_attrs->{bind} = [ @{ $old_attrs->{bind} || [] }, @{ $call_attrs->{bind} || [] } ]; | ||
| 531 | } | ||||
| 532 | |||||
| 533 | |||||
| 534 | 3000 | 2.45ms | for ($old_where, $call_cond) { | ||
| 535 | 6000 | 5.46ms | if (defined $_) { | ||
| 536 | 3000 | 11.0ms | 3000 | 22.2ms | $new_attrs->{where} = $self->_stack_cond ( # spent 22.2ms making 3000 calls to DBIx::Class::ResultSet::_stack_cond, avg 7µs/call |
| 537 | $_, $new_attrs->{where} | ||||
| 538 | ); | ||||
| 539 | } | ||||
| 540 | } | ||||
| 541 | |||||
| 542 | 3000 | 852µs | if (defined $old_having) { | ||
| 543 | $new_attrs->{having} = $self->_stack_cond ( | ||||
| 544 | $old_having, $new_attrs->{having} | ||||
| 545 | ) | ||||
| 546 | } | ||||
| 547 | |||||
| 548 | 3000 | 7.17ms | 3000 | 170ms | my $rs = (ref $self)->new($rsrc, $new_attrs); # spent 170ms making 3000 calls to DBIx::Class::ResultSet::new, avg 57µs/call |
| 549 | |||||
| 550 | 3000 | 615µs | $rs->set_cache($cache) if ($cache); | ||
| 551 | |||||
| 552 | 3000 | 11.3ms | return $rs; | ||
| 553 | } | ||||
| 554 | |||||
| 555 | 1 | 200ns | my $dark_sel_dumper; | ||
| 556 | sub _normalize_selection { | ||||
| 557 | 9000 | 2.93ms | my ($self, $attrs) = @_; | ||
| 558 | |||||
| 559 | # legacy syntax | ||||
| 560 | 9000 | 3.60ms | if ( exists $attrs->{include_columns} ) { | ||
| 561 | carp_unique( "Resultset attribute 'include_columns' is deprecated, use '+columns' instead" ); | ||||
| 562 | $attrs->{'+columns'} = $self->_merge_attr( | ||||
| 563 | $attrs->{'+columns'}, delete $attrs->{include_columns} | ||||
| 564 | ); | ||||
| 565 | } | ||||
| 566 | |||||
| 567 | # columns are always placed first, however | ||||
| 568 | |||||
| 569 | # Keep the X vs +X separation until _resolved_attrs time - this allows to | ||||
| 570 | # delay the decision on whether to use a default select list ($rsrc->columns) | ||||
| 571 | # allowing stuff like the remove_columns helper to work | ||||
| 572 | # | ||||
| 573 | # select/as +select/+as pairs need special handling - the amount of select/as | ||||
| 574 | # elements in each pair does *not* have to be equal (think multicolumn | ||||
| 575 | # selectors like distinct(foo, bar) ). If the selector is bare (no 'as' | ||||
| 576 | # supplied at all) - try to infer the alias, either from the -as parameter | ||||
| 577 | # of the selector spec, or use the parameter whole if it looks like a column | ||||
| 578 | # name (ugly legacy heuristic). If all fails - leave the selector bare (which | ||||
| 579 | # is ok as well), but make sure no more additions to the 'as' chain take place | ||||
| 580 | 9000 | 72.3ms | for my $pref ('', '+') { | ||
| 581 | |||||
| 582 | 36000 | 13.1ms | my ($sel, $as) = map { | ||
| 583 | 18000 | 25.0ms | my $key = "${pref}${_}"; | ||
| 584 | |||||
| 585 | my $val = [ ref $attrs->{$key} eq 'ARRAY' | ||||
| 586 | 36000 | 23.2ms | ? @{$attrs->{$key}} | ||
| 587 | : $attrs->{$key} || () | ||||
| 588 | ]; | ||||
| 589 | 36000 | 7.53ms | delete $attrs->{$key}; | ||
| 590 | 36000 | 8.98ms | $val; | ||
| 591 | } qw/select as/; | ||||
| 592 | |||||
| 593 | 18000 | 7.03ms | if (! @$as and ! @$sel ) { | ||
| 594 | 18000 | 7.02ms | next; | ||
| 595 | } | ||||
| 596 | elsif (@$as and ! @$sel) { | ||||
| 597 | $self->throw_exception( | ||||
| 598 | "Unable to handle ${pref}as specification (@$as) without a corresponding ${pref}select" | ||||
| 599 | ); | ||||
| 600 | } | ||||
| 601 | elsif( ! @$as ) { | ||||
| 602 | # no as part supplied at all - try to deduce (unless explicit end of named selection is declared) | ||||
| 603 | # if any @$as has been supplied we assume the user knows what (s)he is doing | ||||
| 604 | # and blindly keep stacking up pieces | ||||
| 605 | unless ($attrs->{_dark_selector}) { | ||||
| 606 | SELECTOR: | ||||
| 607 | for (@$sel) { | ||||
| 608 | if ( ref $_ eq 'HASH' and exists $_->{-as} ) { | ||||
| 609 | push @$as, $_->{-as}; | ||||
| 610 | } | ||||
| 611 | # assume any plain no-space, no-parenthesis string to be a column spec | ||||
| 612 | # FIXME - this is retarded but is necessary to support shit like 'count(foo)' | ||||
| 613 | elsif ( ! ref $_ and $_ =~ /^ [^\s\(\)]+ $/x) { | ||||
| 614 | push @$as, $_; | ||||
| 615 | } | ||||
| 616 | # if all else fails - raise a flag that no more aliasing will be allowed | ||||
| 617 | else { | ||||
| 618 | $attrs->{_dark_selector} = { | ||||
| 619 | plus_stage => $pref, | ||||
| 620 | string => ($dark_sel_dumper ||= do { | ||||
| 621 | require Data::Dumper::Concise; | ||||
| 622 | Data::Dumper::Concise::DumperObject()->Indent(0); | ||||
| 623 | })->Values([$_])->Dump | ||||
| 624 | , | ||||
| 625 | }; | ||||
| 626 | last SELECTOR; | ||||
| 627 | } | ||||
| 628 | } | ||||
| 629 | } | ||||
| 630 | } | ||||
| 631 | elsif (@$as < @$sel) { | ||||
| 632 | $self->throw_exception( | ||||
| 633 | "Unable to handle an ${pref}as specification (@$as) with less elements than the corresponding ${pref}select" | ||||
| 634 | ); | ||||
| 635 | } | ||||
| 636 | elsif ($pref and $attrs->{_dark_selector}) { | ||||
| 637 | $self->throw_exception( | ||||
| 638 | "Unable to process named '+select', resultset contains an unnamed selector $attrs->{_dark_selector}{string}" | ||||
| 639 | ); | ||||
| 640 | } | ||||
| 641 | |||||
| 642 | |||||
| 643 | # merge result | ||||
| 644 | $attrs->{"${pref}select"} = $self->_merge_attr($attrs->{"${pref}select"}, $sel); | ||||
| 645 | $attrs->{"${pref}as"} = $self->_merge_attr($attrs->{"${pref}as"}, $as); | ||||
| 646 | } | ||||
| 647 | } | ||||
| 648 | |||||
| 649 | # spent 22.2ms within DBIx::Class::ResultSet::_stack_cond which was called 3000 times, avg 7µs/call:
# 3000 times (22.2ms+0s) by DBIx::Class::ResultSet::search_rs at line 536, avg 7µs/call | ||||
| 650 | 3000 | 4.72ms | my ($self, $left, $right) = @_; | ||
| 651 | |||||
| 652 | ( | ||||
| 653 | (ref $_ eq 'ARRAY' and !@$_) | ||||
| 654 | or | ||||
| 655 | (ref $_ eq 'HASH' and ! keys %$_) | ||||
| 656 | 3000 | 9.99ms | ) and $_ = undef for ($left, $right); | ||
| 657 | |||||
| 658 | # either one of the two undef | ||||
| 659 | 3000 | 11.3ms | if ( (defined $left) xor (defined $right) ) { | ||
| 660 | return defined $left ? $left : $right; | ||||
| 661 | } | ||||
| 662 | # both undef | ||||
| 663 | elsif ( ! defined $left ) { | ||||
| 664 | return undef | ||||
| 665 | } | ||||
| 666 | else { | ||||
| 667 | return $self->result_source->schema->storage->_collapse_cond({ -and => [$left, $right] }); | ||||
| 668 | } | ||||
| 669 | } | ||||
| 670 | |||||
| 671 | =head2 search_literal | ||||
| 672 | |||||
| 673 | B<CAVEAT>: C<search_literal> is provided for Class::DBI compatibility and | ||||
| 674 | should only be used in that context. C<search_literal> is a convenience | ||||
| 675 | method. It is equivalent to calling C<< $schema->search(\[]) >>, but if you | ||||
| 676 | want to ensure columns are bound correctly, use L</search>. | ||||
| 677 | |||||
| 678 | See L<DBIx::Class::Manual::Cookbook/SEARCHING> and | ||||
| 679 | L<DBIx::Class::Manual::FAQ/Searching> for searching techniques that do not | ||||
| 680 | require C<search_literal>. | ||||
| 681 | |||||
| 682 | =over 4 | ||||
| 683 | |||||
| 684 | =item Arguments: $sql_fragment, @standalone_bind_values | ||||
| 685 | |||||
| 686 | =item Return Value: L<$resultset|/search> (scalar context) | L<@result_objs|DBIx::Class::Manual::ResultClass> (list context) | ||||
| 687 | |||||
| 688 | =back | ||||
| 689 | |||||
| 690 | my @cds = $cd_rs->search_literal('year = ? AND title = ?', qw/2001 Reload/); | ||||
| 691 | my $newrs = $artist_rs->search_literal('name = ?', 'Metallica'); | ||||
| 692 | |||||
| 693 | Pass a literal chunk of SQL to be added to the conditional part of the | ||||
| 694 | resultset query. | ||||
| 695 | |||||
| 696 | Example of how to use C<search> instead of C<search_literal> | ||||
| 697 | |||||
| 698 | my @cds = $cd_rs->search_literal('cdid = ? AND (artist = ? OR artist = ?)', (2, 1, 2)); | ||||
| 699 | my @cds = $cd_rs->search(\[ 'cdid = ? AND (artist = ? OR artist = ?)', [ 'cdid', 2 ], [ 'artist', 1 ], [ 'artist', 2 ] ]); | ||||
| 700 | |||||
| 701 | =cut | ||||
| 702 | |||||
| 703 | sub search_literal { | ||||
| 704 | my ($self, $sql, @bind) = @_; | ||||
| 705 | my $attr; | ||||
| 706 | if ( @bind && ref($bind[-1]) eq 'HASH' ) { | ||||
| 707 | $attr = pop @bind; | ||||
| 708 | } | ||||
| 709 | return $self->search(\[ $sql, map [ {} => $_ ], @bind ], ($attr || () )); | ||||
| 710 | } | ||||
| 711 | |||||
| 712 | =head2 find | ||||
| 713 | |||||
| 714 | =over 4 | ||||
| 715 | |||||
| 716 | =item Arguments: \%columns_values | @pk_values, { key => $unique_constraint, L<%attrs|/ATTRIBUTES> }? | ||||
| 717 | |||||
| 718 | =item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | undef | ||||
| 719 | |||||
| 720 | =back | ||||
| 721 | |||||
| 722 | Finds and returns a single row based on supplied criteria. Takes either a | ||||
| 723 | hashref with the same format as L</create> (including inference of foreign | ||||
| 724 | keys from related objects), or a list of primary key values in the same | ||||
| 725 | order as the L<primary columns|DBIx::Class::ResultSource/primary_columns> | ||||
| 726 | declaration on the L</result_source>. | ||||
| 727 | |||||
| 728 | In either case an attempt is made to combine conditions already existing on | ||||
| 729 | the resultset with the condition passed to this method. | ||||
| 730 | |||||
| 731 | To aid with preparing the correct query for the storage you may supply the | ||||
| 732 | C<key> attribute, which is the name of a | ||||
| 733 | L<unique constraint|DBIx::Class::ResultSource/add_unique_constraint> (the | ||||
| 734 | unique constraint corresponding to the | ||||
| 735 | L<primary columns|DBIx::Class::ResultSource/primary_columns> is always named | ||||
| 736 | C<primary>). If the C<key> attribute has been supplied, and DBIC is unable | ||||
| 737 | to construct a query that satisfies the named unique constraint fully ( | ||||
| 738 | non-NULL values for each column member of the constraint) an exception is | ||||
| 739 | thrown. | ||||
| 740 | |||||
| 741 | If no C<key> is specified, the search is carried over all unique constraints | ||||
| 742 | which are fully defined by the available condition. | ||||
| 743 | |||||
| 744 | If no such constraint is found, C<find> currently defaults to a simple | ||||
| 745 | C<< search->(\%column_values) >> which may or may not do what you expect. | ||||
| 746 | Note that this fallback behavior may be deprecated in further versions. If | ||||
| 747 | you need to search with arbitrary conditions - use L</search>. If the query | ||||
| 748 | resulting from this fallback produces more than one row, a warning to the | ||||
| 749 | effect is issued, though only the first row is constructed and returned as | ||||
| 750 | C<$result_object>. | ||||
| 751 | |||||
| 752 | In addition to C<key>, L</find> recognizes and applies standard | ||||
| 753 | L<resultset attributes|/ATTRIBUTES> in the same way as L</search> does. | ||||
| 754 | |||||
| 755 | Note that if you have extra concerns about the correctness of the resulting | ||||
| 756 | query you need to specify the C<key> attribute and supply the entire condition | ||||
| 757 | as an argument to find (since it is not always possible to perform the | ||||
| 758 | combination of the resultset condition with the supplied one, especially if | ||||
| 759 | the resultset condition contains literal sql). | ||||
| 760 | |||||
| 761 | For example, to find a row by its primary key: | ||||
| 762 | |||||
| 763 | my $cd = $schema->resultset('CD')->find(5); | ||||
| 764 | |||||
| 765 | You can also find a row by a specific unique constraint: | ||||
| 766 | |||||
| 767 | my $cd = $schema->resultset('CD')->find( | ||||
| 768 | { | ||||
| 769 | artist => 'Massive Attack', | ||||
| 770 | title => 'Mezzanine', | ||||
| 771 | }, | ||||
| 772 | { key => 'cd_artist_title' } | ||||
| 773 | ); | ||||
| 774 | |||||
| 775 | See also L</find_or_create> and L</update_or_create>. | ||||
| 776 | |||||
| 777 | =cut | ||||
| 778 | |||||
| 779 | sub find { | ||||
| 780 | 3000 | 1.39ms | my $self = shift; | ||
| 781 | 3000 | 3.46ms | my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {}); | ||
| 782 | |||||
| 783 | 3000 | 2.40ms | 2 | 113µs | my $rsrc = $self->result_source; # spent 113µs making 2 calls to DBIx::Class::ResultSet::result_source, avg 56µs/call |
| 784 | |||||
| 785 | 3000 | 884µs | my $constraint_name; | ||
| 786 | 3000 | 1.46ms | if (exists $attrs->{key}) { | ||
| 787 | $constraint_name = defined $attrs->{key} | ||||
| 788 | ? $attrs->{key} | ||||
| 789 | : $self->throw_exception("An undefined 'key' resultset attribute makes no sense") | ||||
| 790 | ; | ||||
| 791 | } | ||||
| 792 | |||||
| 793 | # Parse out the condition from input | ||||
| 794 | 3000 | 667µs | my $call_cond; | ||
| 795 | |||||
| 796 | 3000 | 2.93ms | if (ref $_[0] eq 'HASH') { | ||
| 797 | $call_cond = { %{$_[0]} }; | ||||
| 798 | } | ||||
| 799 | else { | ||||
| 800 | # if only values are supplied we need to default to 'primary' | ||||
| 801 | 3000 | 1.57ms | $constraint_name = 'primary' unless defined $constraint_name; | ||
| 802 | |||||
| 803 | 3000 | 9.65ms | 3000 | 48.6ms | my @c_cols = $rsrc->unique_constraint_columns($constraint_name); # spent 48.6ms making 3000 calls to DBIx::Class::ResultSource::unique_constraint_columns, avg 16µs/call |
| 804 | |||||
| 805 | 3000 | 1.36ms | $self->throw_exception( | ||
| 806 | "No constraint columns, maybe a malformed '$constraint_name' constraint?" | ||||
| 807 | ) unless @c_cols; | ||||
| 808 | |||||
| 809 | 3000 | 1.56ms | $self->throw_exception ( | ||
| 810 | 'find() expects either a column/value hashref, or a list of values ' | ||||
| 811 | . "corresponding to the columns of the specified unique constraint '$constraint_name'" | ||||
| 812 | ) unless @c_cols == @_; | ||||
| 813 | |||||
| 814 | 3000 | 5.69ms | @{$call_cond}{@c_cols} = @_; | ||
| 815 | } | ||||
| 816 | |||||
| 817 | # process relationship data if any | ||||
| 818 | 3000 | 11.0ms | for my $key (keys %$call_cond) { | ||
| 819 | 3000 | 4.23ms | if ( | ||
| 820 | length ref($call_cond->{$key}) | ||||
| 821 | and | ||||
| 822 | my $relinfo = $rsrc->relationship_info($key) | ||||
| 823 | and | ||||
| 824 | (ref (my $val = delete $call_cond->{$key}) ne 'ARRAY' ) | ||||
| 825 | |||||
| 826 | ) { | ||||
| 827 | my ($rel_cond, $crosstable) = $rsrc->_resolve_condition( | ||||
| 828 | $relinfo->{cond}, $val, $key, $key | ||||
| 829 | ); | ||||
| 830 | |||||
| 831 | $self->throw_exception("Complex condition via relationship '$key' is unsupported in find()") | ||||
| 832 | if $crosstable or ref($rel_cond) ne 'HASH'; | ||||
| 833 | |||||
| 834 | # supplement condition | ||||
| 835 | # relationship conditions take precedence (?) | ||||
| 836 | @{$call_cond}{keys %$rel_cond} = values %$rel_cond; | ||||
| 837 | } | ||||
| 838 | } | ||||
| 839 | |||||
| 840 | 3000 | 3.09ms | my $alias = exists $attrs->{alias} ? $attrs->{alias} : $self->{attrs}{alias}; | ||
| 841 | 3000 | 761µs | my $final_cond; | ||
| 842 | 3000 | 32.7ms | 9001 | 729ms | if (defined $constraint_name) { # spent 612ms making 3000 calls to DBIx::Class::ResultSource::_minimal_valueset_satisfying_constraint, avg 204µs/call
# spent 82.5ms making 3000 calls to DBIx::Class::ResultSet::_merge_with_rscond, avg 27µs/call
# spent 34.5ms making 3000 calls to DBIx::Class::ResultSet::_qualify_cond_columns, avg 11µs/call
# spent 1µs making 1 call to DBIx::Class::ResultSet::result_source |
| 843 | $final_cond = $self->_qualify_cond_columns ( | ||||
| 844 | |||||
| 845 | $self->result_source->_minimal_valueset_satisfying_constraint( | ||||
| 846 | constraint_name => $constraint_name, | ||||
| 847 | values => ($self->_merge_with_rscond($call_cond))[0], | ||||
| 848 | carp_on_nulls => 1, | ||||
| 849 | ), | ||||
| 850 | |||||
| 851 | $alias, | ||||
| 852 | ); | ||||
| 853 | } | ||||
| 854 | elsif ($self->{attrs}{accessor} and $self->{attrs}{accessor} eq 'single') { | ||||
| 855 | # This means that we got here after a merger of relationship conditions | ||||
| 856 | # in ::Relationship::Base::search_related (the row method), and furthermore | ||||
| 857 | # the relationship is of the 'single' type. This means that the condition | ||||
| 858 | # provided by the relationship (already attached to $self) is sufficient, | ||||
| 859 | # as there can be only one row in the database that would satisfy the | ||||
| 860 | # relationship | ||||
| 861 | } | ||||
| 862 | else { | ||||
| 863 | my (@unique_queries, %seen_column_combinations, $ci, @fc_exceptions); | ||||
| 864 | |||||
| 865 | # no key was specified - fall down to heuristics mode: | ||||
| 866 | # run through all unique queries registered on the resultset, and | ||||
| 867 | # 'OR' all qualifying queries together | ||||
| 868 | # | ||||
| 869 | # always start from 'primary' if it exists at all | ||||
| 870 | for my $c_name ( sort { | ||||
| 871 | $a eq 'primary' ? -1 | ||||
| 872 | : $b eq 'primary' ? 1 | ||||
| 873 | : $a cmp $b | ||||
| 874 | } $rsrc->unique_constraint_names) { | ||||
| 875 | |||||
| 876 | next if $seen_column_combinations{ | ||||
| 877 | join "\x00", sort $rsrc->unique_constraint_columns($c_name) | ||||
| 878 | }++; | ||||
| 879 | |||||
| 880 | try { | ||||
| 881 | push @unique_queries, $self->_qualify_cond_columns( | ||||
| 882 | $self->result_source->_minimal_valueset_satisfying_constraint( | ||||
| 883 | constraint_name => $c_name, | ||||
| 884 | values => ($self->_merge_with_rscond($call_cond))[0], | ||||
| 885 | columns_info => ($ci ||= $self->result_source->columns_info), | ||||
| 886 | ), | ||||
| 887 | $alias | ||||
| 888 | ); | ||||
| 889 | } | ||||
| 890 | catch { | ||||
| 891 | push @fc_exceptions, $_ if $_ =~ /\bFilterColumn\b/; | ||||
| 892 | }; | ||||
| 893 | } | ||||
| 894 | |||||
| 895 | $final_cond = | ||||
| 896 | @unique_queries ? \@unique_queries | ||||
| 897 | : @fc_exceptions ? $self->throw_exception(join "; ", map { $_ =~ /(.*) at .+ line \d+$/s } @fc_exceptions ) | ||||
| 898 | : $self->_non_unique_find_fallback ($call_cond, $attrs) | ||||
| 899 | ; | ||||
| 900 | } | ||||
| 901 | |||||
| 902 | # Run the query, passing the result_class since it should propagate for find | ||||
| 903 | 3000 | 16.0ms | 6000 | 468ms | my $rs = $self->search ($final_cond, {result_class => $self->result_class, %$attrs}); # spent 460ms making 3000 calls to DBIx::Class::ResultSet::search, avg 153µs/call
# spent 7.42ms making 3000 calls to DBIx::Class::ResultSet::result_class, avg 2µs/call |
| 904 | 3000 | 7.05ms | 3000 | 1.35s | if ($rs->_resolved_attrs->{collapse}) { # spent 1.35s making 3000 calls to DBIx::Class::ResultSet::_resolved_attrs, avg 451µs/call |
| 905 | my $row = $rs->next; | ||||
| 906 | carp "Query returned more than one row" if $rs->next; | ||||
| 907 | return $row; | ||||
| 908 | } | ||||
| 909 | else { | ||||
| 910 | 3000 | 22.1ms | 3000 | 8.23s | return $rs->single; # spent 8.23s making 3000 calls to DBIx::Class::ResultSet::single, avg 2.74ms/call |
| 911 | } | ||||
| 912 | } | ||||
| 913 | |||||
| 914 | # This is a stop-gap method as agreed during the discussion on find() cleanup: | ||||
| 915 | # http://lists.scsys.co.uk/pipermail/dbix-class/2010-October/009535.html | ||||
| 916 | # | ||||
| 917 | # It is invoked when find() is called in legacy-mode with insufficiently-unique | ||||
| 918 | # condition. It is provided for overrides until a saner way forward is devised | ||||
| 919 | # | ||||
| 920 | # *NOTE* This is not a public method, and it's *GUARANTEED* to disappear down | ||||
| 921 | # the road. Please adjust your tests accordingly to catch this situation early | ||||
| 922 | # DBIx::Class::ResultSet->can('_non_unique_find_fallback') is reasonable | ||||
| 923 | # | ||||
| 924 | # The method will not be removed without an adequately complete replacement | ||||
| 925 | # for strict-mode enforcement | ||||
| 926 | sub _non_unique_find_fallback { | ||||
| 927 | my ($self, $cond, $attrs) = @_; | ||||
| 928 | |||||
| 929 | return $self->_qualify_cond_columns( | ||||
| 930 | $cond, | ||||
| 931 | exists $attrs->{alias} | ||||
| 932 | ? $attrs->{alias} | ||||
| 933 | : $self->{attrs}{alias} | ||||
| 934 | ); | ||||
| 935 | } | ||||
| 936 | |||||
| 937 | |||||
| 938 | # spent 34.5ms (31.9+2.55) within DBIx::Class::ResultSet::_qualify_cond_columns which was called 3000 times, avg 11µs/call:
# 3000 times (31.9ms+2.55ms) by Koha::Objects::find at line 842, avg 11µs/call | ||||
| 939 | 3000 | 1.93ms | my ($self, $cond, $alias) = @_; | ||
| 940 | |||||
| 941 | 3000 | 5.27ms | my %aliased = %$cond; | ||
| 942 | 3000 | 3.84ms | for (keys %aliased) { | ||
| 943 | 3000 | 17.0ms | 3000 | 2.55ms | $aliased{"$alias.$_"} = delete $aliased{$_} # spent 2.55ms making 3000 calls to DBIx::Class::ResultSet::CORE:match, avg 850ns/call |
| 944 | if $_ !~ /\./; | ||||
| 945 | } | ||||
| 946 | |||||
| 947 | 3000 | 17.8ms | return \%aliased; | ||
| 948 | } | ||||
| 949 | |||||
| 950 | sub _build_unique_cond { | ||||
| 951 | carp_unique sprintf | ||||
| 952 | '_build_unique_cond is a private method, and moreover is about to go ' | ||||
| 953 | . 'away. Please contact the development team at %s if you believe you ' | ||||
| 954 | . 'have a genuine use for this method, in order to discuss alternatives.', | ||||
| 955 | DBIx::Class::_ENV_::HELP_URL, | ||||
| 956 | ; | ||||
| 957 | |||||
| 958 | my ($self, $constraint_name, $cond, $croak_on_null) = @_; | ||||
| 959 | |||||
| 960 | $self->result_source->_minimal_valueset_satisfying_constraint( | ||||
| 961 | constraint_name => $constraint_name, | ||||
| 962 | values => $cond, | ||||
| 963 | carp_on_nulls => !$croak_on_null | ||||
| 964 | ); | ||||
| 965 | } | ||||
| 966 | |||||
| 967 | =head2 search_related | ||||
| 968 | |||||
| 969 | =over 4 | ||||
| 970 | |||||
| 971 | =item Arguments: $rel_name, $cond?, L<\%attrs?|/ATTRIBUTES> | ||||
| 972 | |||||
| 973 | =item Return Value: L<$resultset|/search> (scalar context) | L<@result_objs|DBIx::Class::Manual::ResultClass> (list context) | ||||
| 974 | |||||
| 975 | =back | ||||
| 976 | |||||
| 977 | $new_rs = $cd_rs->search_related('artist', { | ||||
| 978 | name => 'Emo-R-Us', | ||||
| 979 | }); | ||||
| 980 | |||||
| 981 | Searches the specified relationship, optionally specifying a condition and | ||||
| 982 | attributes for matching records. See L</ATTRIBUTES> for more information. | ||||
| 983 | |||||
| 984 | In list context, C<< ->all() >> is called implicitly on the resultset, thus | ||||
| 985 | returning a list of result objects instead. To avoid that, use L</search_related_rs>. | ||||
| 986 | |||||
| 987 | See also L</search_related_rs>. | ||||
| 988 | |||||
| 989 | =cut | ||||
| 990 | |||||
| 991 | sub search_related { | ||||
| 992 | return shift->related_resultset(shift)->search(@_); | ||||
| 993 | } | ||||
| 994 | |||||
| 995 | =head2 search_related_rs | ||||
| 996 | |||||
| 997 | This method works exactly the same as search_related, except that | ||||
| 998 | it guarantees a resultset, even in list context. | ||||
| 999 | |||||
| 1000 | =cut | ||||
| 1001 | |||||
| 1002 | sub search_related_rs { | ||||
| 1003 | return shift->related_resultset(shift)->search_rs(@_); | ||||
| 1004 | } | ||||
| 1005 | |||||
| 1006 | =head2 cursor | ||||
| 1007 | |||||
| 1008 | =over 4 | ||||
| 1009 | |||||
| 1010 | =item Arguments: none | ||||
| 1011 | |||||
| 1012 | =item Return Value: L<$cursor|DBIx::Class::Cursor> | ||||
| 1013 | |||||
| 1014 | =back | ||||
| 1015 | |||||
| 1016 | Returns a storage-driven cursor to the given resultset. See | ||||
| 1017 | L<DBIx::Class::Cursor> for more information. | ||||
| 1018 | |||||
| 1019 | =cut | ||||
| 1020 | |||||
| 1021 | sub cursor { | ||||
| 1022 | my $self = shift; | ||||
| 1023 | |||||
| 1024 | return $self->{cursor} ||= do { | ||||
| 1025 | my $attrs = $self->_resolved_attrs; | ||||
| 1026 | $self->result_source->storage->select( | ||||
| 1027 | $attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs | ||||
| 1028 | ); | ||||
| 1029 | }; | ||||
| 1030 | } | ||||
| 1031 | |||||
| 1032 | =head2 single | ||||
| 1033 | |||||
| 1034 | =over 4 | ||||
| 1035 | |||||
| 1036 | =item Arguments: L<$cond?|DBIx::Class::SQLMaker> | ||||
| 1037 | |||||
| 1038 | =item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | undef | ||||
| 1039 | |||||
| 1040 | =back | ||||
| 1041 | |||||
| 1042 | my $cd = $schema->resultset('CD')->single({ year => 2001 }); | ||||
| 1043 | |||||
| 1044 | Inflates the first result without creating a cursor if the resultset has | ||||
| 1045 | any records in it; if not returns C<undef>. Used by L</find> as a lean version | ||||
| 1046 | of L</search>. | ||||
| 1047 | |||||
| 1048 | While this method can take an optional search condition (just like L</search>) | ||||
| 1049 | being a fast-code-path it does not recognize search attributes. If you need to | ||||
| 1050 | add extra joins or similar, call L</search> and then chain-call L</single> on the | ||||
| 1051 | L<DBIx::Class::ResultSet> returned. | ||||
| 1052 | |||||
| 1053 | =over | ||||
| 1054 | |||||
| 1055 | =item B<Note> | ||||
| 1056 | |||||
| 1057 | As of 0.08100, this method enforces the assumption that the preceding | ||||
| 1058 | query returns only one row. If more than one row is returned, you will receive | ||||
| 1059 | a warning: | ||||
| 1060 | |||||
| 1061 | Query returned more than one row | ||||
| 1062 | |||||
| 1063 | In this case, you should be using L</next> or L</find> instead, or if you really | ||||
| 1064 | know what you are doing, use the L</rows> attribute to explicitly limit the size | ||||
| 1065 | of the resultset. | ||||
| 1066 | |||||
| 1067 | This method will also throw an exception if it is called on a resultset prefetching | ||||
| 1068 | has_many, as such a prefetch implies fetching multiple rows from the database in | ||||
| 1069 | order to assemble the resulting object. | ||||
| 1070 | |||||
| 1071 | =back | ||||
| 1072 | |||||
| 1073 | =cut | ||||
| 1074 | |||||
| 1075 | # spent 8.23s (122ms+8.11) within DBIx::Class::ResultSet::single which was called 3000 times, avg 2.74ms/call:
# 3000 times (122ms+8.11s) by Koha::Objects::find at line 910, avg 2.74ms/call | ||||
| 1076 | 3000 | 1.64ms | my ($self, $where) = @_; | ||
| 1077 | 3000 | 1.65ms | if(@_ > 2) { | ||
| 1078 | $self->throw_exception('single() only takes search conditions, no attributes. You want ->search( $cond, $attrs )->single()'); | ||||
| 1079 | } | ||||
| 1080 | |||||
| 1081 | 3000 | 18.7ms | 3000 | 4.57ms | my $attrs = { %{$self->_resolved_attrs} }; # spent 4.57ms making 3000 calls to DBIx::Class::ResultSet::_resolved_attrs, avg 2µs/call |
| 1082 | |||||
| 1083 | 3000 | 1.51ms | $self->throw_exception( | ||
| 1084 | 'single() can not be used on resultsets collapsing a has_many. Use find( \%cond ) or next() instead' | ||||
| 1085 | ) if $attrs->{collapse}; | ||||
| 1086 | |||||
| 1087 | 3000 | 684µs | if ($where) { | ||
| 1088 | if (defined $attrs->{where}) { | ||||
| 1089 | $attrs->{where} = { | ||||
| 1090 | '-and' => | ||||
| 1091 | [ map { ref $_ eq 'ARRAY' ? [ -or => $_ ] : $_ } | ||||
| 1092 | $where, delete $attrs->{where} ] | ||||
| 1093 | }; | ||||
| 1094 | } else { | ||||
| 1095 | $attrs->{where} = $where; | ||||
| 1096 | } | ||||
| 1097 | } | ||||
| 1098 | |||||
| 1099 | 3000 | 27.5ms | 6001 | 98.7ms | my $data = [ $self->result_source->storage->select_single( # spent 83.1ms making 3000 calls to DBIx::Class::ResultSource::storage, avg 28µs/call
# spent 15.6ms making 3000 calls to DBIx::Class::Storage::DBI::select_single, avg 5µs/call
# spent 2µs making 1 call to DBIx::Class::ResultSet::result_source |
| 1100 | $attrs->{from}, $attrs->{select}, | ||||
| 1101 | $attrs->{where}, $attrs | ||||
| 1102 | )]; | ||||
| 1103 | |||||
| 1104 | 3000 | 2.06ms | return undef unless @$data; | ||
| 1105 | 3000 | 7.32ms | $self->{_stashed_rows} = [ $data ]; | ||
| 1106 | 3000 | 23.8ms | 3000 | 234ms | $self->_construct_results->[0]; # spent 234ms making 3000 calls to DBIx::Class::ResultSet::_construct_results, avg 78µs/call |
| 1107 | } | ||||
| 1108 | |||||
| 1109 | =head2 get_column | ||||
| 1110 | |||||
| 1111 | =over 4 | ||||
| 1112 | |||||
| 1113 | =item Arguments: L<$cond?|DBIx::Class::SQLMaker> | ||||
| 1114 | |||||
| 1115 | =item Return Value: L<$resultsetcolumn|DBIx::Class::ResultSetColumn> | ||||
| 1116 | |||||
| 1117 | =back | ||||
| 1118 | |||||
| 1119 | my $max_length = $rs->get_column('length')->max; | ||||
| 1120 | |||||
| 1121 | Returns a L<DBIx::Class::ResultSetColumn> instance for a column of the ResultSet. | ||||
| 1122 | |||||
| 1123 | =cut | ||||
| 1124 | |||||
| 1125 | sub get_column { | ||||
| 1126 | my ($self, $column) = @_; | ||||
| 1127 | my $new = DBIx::Class::ResultSetColumn->new($self, $column); | ||||
| 1128 | return $new; | ||||
| 1129 | } | ||||
| 1130 | |||||
| 1131 | =head2 search_like | ||||
| 1132 | |||||
| 1133 | =over 4 | ||||
| 1134 | |||||
| 1135 | =item Arguments: L<$cond|DBIx::Class::SQLMaker>, L<\%attrs?|/ATTRIBUTES> | ||||
| 1136 | |||||
| 1137 | =item Return Value: L<$resultset|/search> (scalar context) | L<@result_objs|DBIx::Class::Manual::ResultClass> (list context) | ||||
| 1138 | |||||
| 1139 | =back | ||||
| 1140 | |||||
| 1141 | # WHERE title LIKE '%blue%' | ||||
| 1142 | $cd_rs = $rs->search_like({ title => '%blue%'}); | ||||
| 1143 | |||||
| 1144 | Performs a search, but uses C<LIKE> instead of C<=> as the condition. Note | ||||
| 1145 | that this is simply a convenience method retained for ex Class::DBI users. | ||||
| 1146 | You most likely want to use L</search> with specific operators. | ||||
| 1147 | |||||
| 1148 | For more information, see L<DBIx::Class::Manual::Cookbook>. | ||||
| 1149 | |||||
| 1150 | This method is deprecated and will be removed in 0.09. Use L<search()|/search> | ||||
| 1151 | instead. An example conversion is: | ||||
| 1152 | |||||
| 1153 | ->search_like({ foo => 'bar' }); | ||||
| 1154 | |||||
| 1155 | # Becomes | ||||
| 1156 | |||||
| 1157 | ->search({ foo => { like => 'bar' } }); | ||||
| 1158 | |||||
| 1159 | =cut | ||||
| 1160 | |||||
| 1161 | sub search_like { | ||||
| 1162 | my $class = shift; | ||||
| 1163 | carp_unique ( | ||||
| 1164 | 'search_like() is deprecated and will be removed in DBIC version 0.09.' | ||||
| 1165 | .' Instead use ->search({ x => { -like => "y%" } })' | ||||
| 1166 | .' (note the outer pair of {}s - they are important!)' | ||||
| 1167 | ); | ||||
| 1168 | my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {}); | ||||
| 1169 | my $query = ref $_[0] eq 'HASH' ? { %{shift()} }: {@_}; | ||||
| 1170 | $query->{$_} = { 'like' => $query->{$_} } for keys %$query; | ||||
| 1171 | return $class->search($query, { %$attrs }); | ||||
| 1172 | } | ||||
| 1173 | |||||
| 1174 | =head2 slice | ||||
| 1175 | |||||
| 1176 | =over 4 | ||||
| 1177 | |||||
| 1178 | =item Arguments: $first, $last | ||||
| 1179 | |||||
| 1180 | =item Return Value: L<$resultset|/search> (scalar context) | L<@result_objs|DBIx::Class::Manual::ResultClass> (list context) | ||||
| 1181 | |||||
| 1182 | =back | ||||
| 1183 | |||||
| 1184 | Returns a resultset or object list representing a subset of elements from the | ||||
| 1185 | resultset slice is called on. Indexes are from 0, i.e., to get the first | ||||
| 1186 | three records, call: | ||||
| 1187 | |||||
| 1188 | my ($one, $two, $three) = $rs->slice(0, 2); | ||||
| 1189 | |||||
| 1190 | =cut | ||||
| 1191 | |||||
| 1192 | sub slice { | ||||
| 1193 | my ($self, $min, $max) = @_; | ||||
| 1194 | my $attrs = {}; # = { %{ $self->{attrs} || {} } }; | ||||
| 1195 | $attrs->{offset} = $self->{attrs}{offset} || 0; | ||||
| 1196 | $attrs->{offset} += $min; | ||||
| 1197 | $attrs->{rows} = ($max ? ($max - $min + 1) : 1); | ||||
| 1198 | return $self->search(undef, $attrs); | ||||
| 1199 | } | ||||
| 1200 | |||||
| 1201 | =head2 next | ||||
| 1202 | |||||
| 1203 | =over 4 | ||||
| 1204 | |||||
| 1205 | =item Arguments: none | ||||
| 1206 | |||||
| 1207 | =item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | undef | ||||
| 1208 | |||||
| 1209 | =back | ||||
| 1210 | |||||
| 1211 | Returns the next element in the resultset (C<undef> is there is none). | ||||
| 1212 | |||||
| 1213 | Can be used to efficiently iterate over records in the resultset: | ||||
| 1214 | |||||
| 1215 | my $rs = $schema->resultset('CD')->search; | ||||
| 1216 | while (my $cd = $rs->next) { | ||||
| 1217 | print $cd->title; | ||||
| 1218 | } | ||||
| 1219 | |||||
| 1220 | Note that you need to store the resultset object, and call C<next> on it. | ||||
| 1221 | Calling C<< resultset('Table')->next >> repeatedly will always return the | ||||
| 1222 | first record from the resultset. | ||||
| 1223 | |||||
| 1224 | =cut | ||||
| 1225 | |||||
| 1226 | sub next { | ||||
| 1227 | my ($self) = @_; | ||||
| 1228 | |||||
| 1229 | if (my $cache = $self->get_cache) { | ||||
| 1230 | $self->{all_cache_position} ||= 0; | ||||
| 1231 | return $cache->[$self->{all_cache_position}++]; | ||||
| 1232 | } | ||||
| 1233 | |||||
| 1234 | if ($self->{attrs}{cache}) { | ||||
| 1235 | delete $self->{pager}; | ||||
| 1236 | $self->{all_cache_position} = 1; | ||||
| 1237 | return ($self->all)[0]; | ||||
| 1238 | } | ||||
| 1239 | |||||
| 1240 | return shift(@{$self->{_stashed_results}}) if @{ $self->{_stashed_results}||[] }; | ||||
| 1241 | |||||
| 1242 | $self->{_stashed_results} = $self->_construct_results | ||||
| 1243 | or return undef; | ||||
| 1244 | |||||
| 1245 | return shift @{$self->{_stashed_results}}; | ||||
| 1246 | } | ||||
| 1247 | |||||
| 1248 | # Constructs as many results as it can in one pass while respecting | ||||
| 1249 | # cursor laziness. Several modes of operation: | ||||
| 1250 | # | ||||
| 1251 | # * Always builds everything present in @{$self->{_stashed_rows}} | ||||
| 1252 | # * If called with $fetch_all true - pulls everything off the cursor and | ||||
| 1253 | # builds all result structures (or objects) in one pass | ||||
| 1254 | # * If $self->_resolved_attrs->{collapse} is true, checks the order_by | ||||
| 1255 | # and if the resultset is ordered properly by the left side: | ||||
| 1256 | # * Fetches stuff off the cursor until the "master object" changes, | ||||
| 1257 | # and saves the last extra row (if any) in @{$self->{_stashed_rows}} | ||||
| 1258 | # OR | ||||
| 1259 | # * Just fetches, and collapses/constructs everything as if $fetch_all | ||||
| 1260 | # was requested (there is no other way to collapse except for an | ||||
| 1261 | # eager cursor) | ||||
| 1262 | # * If no collapse is requested - just get the next row, construct and | ||||
| 1263 | # return | ||||
| 1264 | # spent 234ms (169+65.6) within DBIx::Class::ResultSet::_construct_results which was called 3000 times, avg 78µs/call:
# 3000 times (169ms+65.6ms) by DBIx::Class::ResultSet::single at line 1106, avg 78µs/call | ||||
| 1265 | 3000 | 1.62ms | my ($self, $fetch_all) = @_; | ||
| 1266 | |||||
| 1267 | 3000 | 5.23ms | 1 | 1µs | my $rsrc = $self->result_source; # spent 1µs making 1 call to DBIx::Class::ResultSet::result_source |
| 1268 | 3000 | 8.39ms | 3000 | 9.97ms | my $attrs = $self->_resolved_attrs; # spent 9.97ms making 3000 calls to DBIx::Class::ResultSet::_resolved_attrs, avg 3µs/call |
| 1269 | |||||
| 1270 | 3000 | 4.62ms | if ( | ||
| 1271 | ! $fetch_all | ||||
| 1272 | and | ||||
| 1273 | ! $attrs->{order_by} | ||||
| 1274 | and | ||||
| 1275 | $attrs->{collapse} | ||||
| 1276 | and | ||||
| 1277 | my @pcols = $rsrc->primary_columns | ||||
| 1278 | ) { | ||||
| 1279 | # default order for collapsing unless the user asked for something | ||||
| 1280 | $attrs->{order_by} = [ map { join '.', $attrs->{alias}, $_} @pcols ]; | ||||
| 1281 | $attrs->{_ordered_for_collapse} = 1; | ||||
| 1282 | $attrs->{_order_is_artificial} = 1; | ||||
| 1283 | } | ||||
| 1284 | |||||
| 1285 | # this will be used as both initial raw-row collector AND as a RV of | ||||
| 1286 | # _construct_results. Not regrowing the array twice matters a lot... | ||||
| 1287 | # a surprising amount actually | ||||
| 1288 | 3000 | 3.22ms | my $rows = delete $self->{_stashed_rows}; | ||
| 1289 | |||||
| 1290 | 3000 | 524µs | my $cursor; # we may not need one at all | ||
| 1291 | |||||
| 1292 | 3000 | 1.09ms | my $did_fetch_all = $fetch_all; | ||
| 1293 | |||||
| 1294 | 3000 | 2.04ms | if ($fetch_all) { | ||
| 1295 | # FIXME SUBOPTIMAL - we can do better, cursor->next/all (well diff. methods) should return a ref | ||||
| 1296 | $rows = [ ($rows ? @$rows : ()), $self->cursor->all ]; | ||||
| 1297 | } | ||||
| 1298 | elsif( $attrs->{collapse} ) { | ||||
| 1299 | |||||
| 1300 | # a cursor will need to be closed over in case of collapse | ||||
| 1301 | $cursor = $self->cursor; | ||||
| 1302 | |||||
| 1303 | $attrs->{_ordered_for_collapse} = ( | ||||
| 1304 | ( | ||||
| 1305 | $attrs->{order_by} | ||||
| 1306 | and | ||||
| 1307 | $rsrc->schema | ||||
| 1308 | ->storage | ||||
| 1309 | ->_extract_colinfo_of_stable_main_source_order_by_portion($attrs) | ||||
| 1310 | ) ? 1 : 0 | ||||
| 1311 | ) unless defined $attrs->{_ordered_for_collapse}; | ||||
| 1312 | |||||
| 1313 | if (! $attrs->{_ordered_for_collapse}) { | ||||
| 1314 | $did_fetch_all = 1; | ||||
| 1315 | |||||
| 1316 | # instead of looping over ->next, use ->all in stealth mode | ||||
| 1317 | # *without* calling a ->reset afterwards | ||||
| 1318 | # FIXME ENCAPSULATION - encapsulation breach, cursor method additions pending | ||||
| 1319 | if (! $cursor->{_done}) { | ||||
| 1320 | $rows = [ ($rows ? @$rows : ()), $cursor->all ]; | ||||
| 1321 | $cursor->{_done} = 1; | ||||
| 1322 | } | ||||
| 1323 | } | ||||
| 1324 | } | ||||
| 1325 | |||||
| 1326 | 3000 | 2.31ms | if (! $did_fetch_all and ! @{$rows||[]} ) { | ||
| 1327 | # FIXME SUBOPTIMAL - we can do better, cursor->next/all (well diff. methods) should return a ref | ||||
| 1328 | $cursor ||= $self->cursor; | ||||
| 1329 | if (scalar (my @r = $cursor->next) ) { | ||||
| 1330 | $rows = [ \@r ]; | ||||
| 1331 | } | ||||
| 1332 | } | ||||
| 1333 | |||||
| 1334 | 3000 | 1.89ms | return undef unless @{$rows||[]}; | ||
| 1335 | |||||
| 1336 | # sanity check - people are too clever for their own good | ||||
| 1337 | 3000 | 1.37ms | if ($attrs->{collapse} and my $aliastypes = $attrs->{_last_sqlmaker_alias_map} ) { | ||
| 1338 | |||||
| 1339 | my $multiplied_selectors; | ||||
| 1340 | for my $sel_alias ( grep { $_ ne $attrs->{alias} } keys %{ $aliastypes->{selecting} } ) { | ||||
| 1341 | if ( | ||||
| 1342 | $aliastypes->{multiplying}{$sel_alias} | ||||
| 1343 | or | ||||
| 1344 | $aliastypes->{premultiplied}{$sel_alias} | ||||
| 1345 | ) { | ||||
| 1346 | $multiplied_selectors->{$_} = 1 for values %{$aliastypes->{selecting}{$sel_alias}{-seen_columns}} | ||||
| 1347 | } | ||||
| 1348 | } | ||||
| 1349 | |||||
| 1350 | for my $i (0 .. $#{$attrs->{as}} ) { | ||||
| 1351 | my $sel = $attrs->{select}[$i]; | ||||
| 1352 | |||||
| 1353 | if (ref $sel eq 'SCALAR') { | ||||
| 1354 | $sel = $$sel; | ||||
| 1355 | } | ||||
| 1356 | elsif( ref $sel eq 'REF' and ref $$sel eq 'ARRAY' ) { | ||||
| 1357 | $sel = $$sel->[0]; | ||||
| 1358 | } | ||||
| 1359 | |||||
| 1360 | $self->throw_exception( | ||||
| 1361 | 'Result collapse not possible - selection from a has_many source redirected to the main object' | ||||
| 1362 | ) if ($multiplied_selectors->{$sel} and $attrs->{as}[$i] !~ /\./); | ||||
| 1363 | } | ||||
| 1364 | } | ||||
| 1365 | |||||
| 1366 | # hotspot - skip the setter | ||||
| 1367 | 3000 | 4.06ms | 1 | 1µs | my $res_class = $self->_result_class; # spent 1µs making 1 call to DBIx::Class::ResultSet::_result_class |
| 1368 | |||||
| 1369 | 3000 | 46.7ms | 3000 | 26.8ms | my $inflator_cref = $self->{_result_inflator}{cref} ||= do { # spent 26.8ms making 3000 calls to UNIVERSAL::can, avg 9µs/call |
| 1370 | $res_class->can ('inflate_result') | ||||
| 1371 | or $self->throw_exception("Inflator $res_class does not provide an inflate_result() method"); | ||||
| 1372 | }; | ||||
| 1373 | |||||
| 1374 | 3000 | 1.30ms | my $infmap = $attrs->{as}; | ||
| 1375 | |||||
| 1376 | 3000 | 10.5ms | $self->{_result_inflator}{is_core_row} = ( ( | ||
| 1377 | $inflator_cref | ||||
| 1378 | == | ||||
| 1379 | ( \&DBIx::Class::Row::inflate_result || die "No ::Row::inflate_result() - can't happen" ) | ||||
| 1380 | ) ? 1 : 0 ) unless defined $self->{_result_inflator}{is_core_row}; | ||||
| 1381 | |||||
| 1382 | 3000 | 6.59ms | $self->{_result_inflator}{is_hri} = ( ( | ||
| 1383 | ! $self->{_result_inflator}{is_core_row} | ||||
| 1384 | and | ||||
| 1385 | $inflator_cref == ( | ||||
| 1386 | require DBIx::Class::ResultClass::HashRefInflator | ||||
| 1387 | && | ||||
| 1388 | DBIx::Class::ResultClass::HashRefInflator->can('inflate_result') | ||||
| 1389 | ) | ||||
| 1390 | ) ? 1 : 0 ) unless defined $self->{_result_inflator}{is_hri}; | ||||
| 1391 | |||||
| 1392 | |||||
| 1393 | 3000 | 3.28ms | if ($attrs->{_simple_passthrough_construction}) { | ||
| 1394 | # construct a much simpler array->hash folder for the one-table HRI cases right here | ||||
| 1395 | 3000 | 7.96ms | if ($self->{_result_inflator}{is_hri}) { | ||
| 1396 | for my $r (@$rows) { | ||||
| 1397 | $r = { map { $infmap->[$_] => $r->[$_] } 0..$#$infmap }; | ||||
| 1398 | } | ||||
| 1399 | } | ||||
| 1400 | # FIXME SUBOPTIMAL this is a very very very hot spot | ||||
| 1401 | # while rather optimal we can *still* do much better, by | ||||
| 1402 | # building a smarter Row::inflate_result(), and | ||||
| 1403 | # switch to feeding it data via a much leaner interface | ||||
| 1404 | # | ||||
| 1405 | # crude unscientific benchmarking indicated the shortcut eval is not worth it for | ||||
| 1406 | # this particular resultset size | ||||
| 1407 | elsif ( $self->{_result_inflator}{is_core_row} and @$rows < 60 ) { | ||||
| 1408 | for my $r (@$rows) { | ||||
| 1409 | 3000 | 61.5ms | 3000 | 28.8ms | $r = $inflator_cref->($res_class, $rsrc, { map { $infmap->[$_] => $r->[$_] } (0..$#$infmap) } ); # spent 28.8ms making 3000 calls to DBIx::Class::Row::inflate_result, avg 10µs/call |
| 1410 | } | ||||
| 1411 | } | ||||
| 1412 | else { | ||||
| 1413 | eval sprintf ( | ||||
| 1414 | ( $self->{_result_inflator}{is_core_row} | ||||
| 1415 | ? '$_ = $inflator_cref->($res_class, $rsrc, { %s }) for @$rows' | ||||
| 1416 | # a custom inflator may be a multiplier/reductor - put it in direct list ctx | ||||
| 1417 | : '@$rows = map { $inflator_cref->($res_class, $rsrc, { %s } ) } @$rows' | ||||
| 1418 | ), | ||||
| 1419 | ( join (', ', map { "\$infmap->[$_] => \$_->[$_]" } 0..$#$infmap ) ) | ||||
| 1420 | ); | ||||
| 1421 | } | ||||
| 1422 | } | ||||
| 1423 | else { | ||||
| 1424 | my $parser_type = | ||||
| 1425 | $self->{_result_inflator}{is_hri} ? 'hri' | ||||
| 1426 | : $self->{_result_inflator}{is_core_row} ? 'classic_pruning' | ||||
| 1427 | : 'classic_nonpruning' | ||||
| 1428 | ; | ||||
| 1429 | |||||
| 1430 | # $args and $attrs to _mk_row_parser are separated to delineate what is | ||||
| 1431 | # core collapser stuff and what is dbic $rs specific | ||||
| 1432 | @{$self->{_row_parser}{$parser_type}}{qw(cref nullcheck)} = $rsrc->_mk_row_parser({ | ||||
| 1433 | eval => 1, | ||||
| 1434 | inflate_map => $infmap, | ||||
| 1435 | collapse => $attrs->{collapse}, | ||||
| 1436 | premultiplied => $attrs->{_main_source_premultiplied}, | ||||
| 1437 | hri_style => $self->{_result_inflator}{is_hri}, | ||||
| 1438 | prune_null_branches => $self->{_result_inflator}{is_hri} || $self->{_result_inflator}{is_core_row}, | ||||
| 1439 | }, $attrs) unless $self->{_row_parser}{$parser_type}{cref}; | ||||
| 1440 | |||||
| 1441 | # column_info metadata historically hasn't been too reliable. | ||||
| 1442 | # We need to start fixing this somehow (the collapse resolver | ||||
| 1443 | # can't work without it). Add an explicit check for the *main* | ||||
| 1444 | # result, hopefully this will gradually weed out such errors | ||||
| 1445 | # | ||||
| 1446 | # FIXME - this is a temporary kludge that reduces performance | ||||
| 1447 | # It is however necessary for the time being | ||||
| 1448 | my ($unrolled_non_null_cols_to_check, $err); | ||||
| 1449 | |||||
| 1450 | if (my $check_non_null_cols = $self->{_row_parser}{$parser_type}{nullcheck} ) { | ||||
| 1451 | |||||
| 1452 | $err = | ||||
| 1453 | 'Collapse aborted due to invalid ResultSource metadata - the following ' | ||||
| 1454 | . 'selections are declared non-nullable but NULLs were retrieved: ' | ||||
| 1455 | ; | ||||
| 1456 | |||||
| 1457 | my @violating_idx; | ||||
| 1458 | COL: for my $i (@$check_non_null_cols) { | ||||
| 1459 | ! defined $_->[$i] and push @violating_idx, $i and next COL for @$rows; | ||||
| 1460 | } | ||||
| 1461 | |||||
| 1462 | $self->throw_exception( $err . join (', ', map { "'$infmap->[$_]'" } @violating_idx ) ) | ||||
| 1463 | if @violating_idx; | ||||
| 1464 | |||||
| 1465 | $unrolled_non_null_cols_to_check = join (',', @$check_non_null_cols); | ||||
| 1466 | |||||
| 1467 | utf8::upgrade($unrolled_non_null_cols_to_check) | ||||
| 1468 | if DBIx::Class::_ENV_::STRESSTEST_UTF8_UPGRADE_GENERATED_COLLAPSER_SOURCE; | ||||
| 1469 | } | ||||
| 1470 | |||||
| 1471 | my $next_cref = | ||||
| 1472 | ($did_fetch_all or ! $attrs->{collapse}) ? undef | ||||
| 1473 | : defined $unrolled_non_null_cols_to_check ? eval sprintf <<'EOS', $unrolled_non_null_cols_to_check | ||||
| 1474 | sub { | ||||
| 1475 | # FIXME SUBOPTIMAL - we can do better, cursor->next/all (well diff. methods) should return a ref | ||||
| 1476 | my @r = $cursor->next or return; | ||||
| 1477 | if (my @violating_idx = grep { ! defined $r[$_] } (%s) ) { | ||||
| 1478 | $self->throw_exception( $err . join (', ', map { "'$infmap->[$_]'" } @violating_idx ) ) | ||||
| 1479 | } | ||||
| 1480 | \@r | ||||
| 1481 | } | ||||
| 1482 | EOS | ||||
| 1483 | : sub { | ||||
| 1484 | # FIXME SUBOPTIMAL - we can do better, cursor->next/all (well diff. methods) should return a ref | ||||
| 1485 | my @r = $cursor->next or return; | ||||
| 1486 | \@r | ||||
| 1487 | } | ||||
| 1488 | ; | ||||
| 1489 | |||||
| 1490 | $self->{_row_parser}{$parser_type}{cref}->( | ||||
| 1491 | $rows, | ||||
| 1492 | $next_cref ? ( $next_cref, $self->{_stashed_rows} = [] ) : (), | ||||
| 1493 | ); | ||||
| 1494 | |||||
| 1495 | # simple in-place substitution, does not regrow $rows | ||||
| 1496 | if ($self->{_result_inflator}{is_core_row}) { | ||||
| 1497 | $_ = $inflator_cref->($res_class, $rsrc, @$_) for @$rows | ||||
| 1498 | } | ||||
| 1499 | # Special-case multi-object HRI - there is no $inflator_cref pass at all | ||||
| 1500 | elsif ( ! $self->{_result_inflator}{is_hri} ) { | ||||
| 1501 | # the inflator may be a multiplier/reductor - put it in list ctx | ||||
| 1502 | @$rows = map { $inflator_cref->($res_class, $rsrc, @$_) } @$rows; | ||||
| 1503 | } | ||||
| 1504 | } | ||||
| 1505 | |||||
| 1506 | # The @$rows check seems odd at first - why wouldn't we want to warn | ||||
| 1507 | # regardless? The issue is things like find() etc, where the user | ||||
| 1508 | # *knows* only one result will come back. In these cases the ->all | ||||
| 1509 | # is not a pessimization, but rather something we actually want | ||||
| 1510 | 3000 | 2.70ms | carp_unique( | ||
| 1511 | 'Unable to properly collapse has_many results in iterator mode due ' | ||||
| 1512 | . 'to order criteria - performed an eager cursor slurp underneath. ' | ||||
| 1513 | . 'Consider using ->all() instead' | ||||
| 1514 | ) if ( ! $fetch_all and @$rows > 1 ); | ||||
| 1515 | |||||
| 1516 | 3000 | 18.8ms | return $rows; | ||
| 1517 | } | ||||
| 1518 | |||||
| 1519 | =head2 result_source | ||||
| 1520 | |||||
| 1521 | =over 4 | ||||
| 1522 | |||||
| 1523 | =item Arguments: L<$result_source?|DBIx::Class::ResultSource> | ||||
| 1524 | |||||
| 1525 | =item Return Value: L<$result_source|DBIx::Class::ResultSource> | ||||
| 1526 | |||||
| 1527 | =back | ||||
| 1528 | |||||
| 1529 | An accessor for the primary ResultSource object from which this ResultSet | ||||
| 1530 | is derived. | ||||
| 1531 | |||||
| 1532 | =head2 result_class | ||||
| 1533 | |||||
| 1534 | =over 4 | ||||
| 1535 | |||||
| 1536 | =item Arguments: $result_class? | ||||
| 1537 | |||||
| 1538 | =item Return Value: $result_class | ||||
| 1539 | |||||
| 1540 | =back | ||||
| 1541 | |||||
| 1542 | An accessor for the class to use when creating result objects. Defaults to | ||||
| 1543 | C<< result_source->result_class >> - which in most cases is the name of the | ||||
| 1544 | L<"table"|DBIx::Class::Manual::Glossary/"ResultSource"> class. | ||||
| 1545 | |||||
| 1546 | Note that changing the result_class will also remove any components | ||||
| 1547 | that were originally loaded in the source class via | ||||
| 1548 | L<load_components|Class::C3::Componentised/load_components( @comps )>. | ||||
| 1549 | Any overloaded methods in the original source class will not run. | ||||
| 1550 | |||||
| 1551 | =cut | ||||
| 1552 | |||||
| 1553 | sub result_class { | ||||
| 1554 | 9000 | 3.59ms | my ($self, $result_class) = @_; | ||
| 1555 | 9000 | 3.88ms | if ($result_class) { | ||
| 1556 | |||||
| 1557 | # don't fire this for an object | ||||
| 1558 | 6000 | 13.8ms | 6000 | 43.0ms | $self->ensure_class_loaded($result_class) # spent 43.0ms making 6000 calls to Class::C3::Componentised::ensure_class_loaded, avg 7µs/call |
| 1559 | unless ref($result_class); | ||||
| 1560 | |||||
| 1561 | 6000 | 14.0ms | 6000 | 9.94ms | if ($self->get_cache) { # spent 9.94ms making 6000 calls to DBIx::Class::ResultSet::get_cache, avg 2µs/call |
| 1562 | carp_unique('Changing the result_class of a ResultSet instance with cached results is a noop - the cache contents will not be altered'); | ||||
| 1563 | } | ||||
| 1564 | # FIXME ENCAPSULATION - encapsulation breach, cursor method additions pending | ||||
| 1565 | elsif ($self->{cursor} && $self->{cursor}{_pos}) { | ||||
| 1566 | $self->throw_exception('Changing the result_class of a ResultSet instance with an active cursor is not supported'); | ||||
| 1567 | } | ||||
| 1568 | |||||
| 1569 | 6000 | 7.30ms | 2 | 160µs | $self->_result_class($result_class); # spent 160µs making 2 calls to DBIx::Class::ResultSet::_result_class, avg 80µs/call |
| 1570 | |||||
| 1571 | 6000 | 4.06ms | delete $self->{_result_inflator}; | ||
| 1572 | } | ||||
| 1573 | 9000 | 27.9ms | 1 | 900ns | $self->_result_class; # spent 900ns making 1 call to DBIx::Class::ResultSet::_result_class |
| 1574 | } | ||||
| 1575 | |||||
| 1576 | =head2 count | ||||
| 1577 | |||||
| 1578 | =over 4 | ||||
| 1579 | |||||
| 1580 | =item Arguments: L<$cond|DBIx::Class::SQLMaker>, L<\%attrs?|/ATTRIBUTES> | ||||
| 1581 | |||||
| 1582 | =item Return Value: $count | ||||
| 1583 | |||||
| 1584 | =back | ||||
| 1585 | |||||
| 1586 | Performs an SQL C<COUNT> with the same query as the resultset was built | ||||
| 1587 | with to find the number of elements. Passing arguments is equivalent to | ||||
| 1588 | C<< $rs->search ($cond, \%attrs)->count >> | ||||
| 1589 | |||||
| 1590 | =cut | ||||
| 1591 | |||||
| 1592 | sub count { | ||||
| 1593 | my $self = shift; | ||||
| 1594 | return $self->search(@_)->count if @_ and defined $_[0]; | ||||
| 1595 | return scalar @{ $self->get_cache } if $self->get_cache; | ||||
| 1596 | |||||
| 1597 | my $attrs = { %{ $self->_resolved_attrs } }; | ||||
| 1598 | |||||
| 1599 | # this is a little optimization - it is faster to do the limit | ||||
| 1600 | # adjustments in software, instead of a subquery | ||||
| 1601 | my ($rows, $offset) = delete @{$attrs}{qw/rows offset/}; | ||||
| 1602 | |||||
| 1603 | my $crs; | ||||
| 1604 | if ($self->_has_resolved_attr (qw/collapse group_by/)) { | ||||
| 1605 | $crs = $self->_count_subq_rs ($attrs); | ||||
| 1606 | } | ||||
| 1607 | else { | ||||
| 1608 | $crs = $self->_count_rs ($attrs); | ||||
| 1609 | } | ||||
| 1610 | my $count = $crs->next; | ||||
| 1611 | |||||
| 1612 | $count -= $offset if $offset; | ||||
| 1613 | $count = $rows if $rows and $rows < $count; | ||||
| 1614 | $count = 0 if ($count < 0); | ||||
| 1615 | |||||
| 1616 | return $count; | ||||
| 1617 | } | ||||
| 1618 | |||||
| 1619 | =head2 count_rs | ||||
| 1620 | |||||
| 1621 | =over 4 | ||||
| 1622 | |||||
| 1623 | =item Arguments: L<$cond|DBIx::Class::SQLMaker>, L<\%attrs?|/ATTRIBUTES> | ||||
| 1624 | |||||
| 1625 | =item Return Value: L<$count_rs|DBIx::Class::ResultSetColumn> | ||||
| 1626 | |||||
| 1627 | =back | ||||
| 1628 | |||||
| 1629 | Same as L</count> but returns a L<DBIx::Class::ResultSetColumn> object. | ||||
| 1630 | This can be very handy for subqueries: | ||||
| 1631 | |||||
| 1632 | ->search( { amount => $some_rs->count_rs->as_query } ) | ||||
| 1633 | |||||
| 1634 | As with regular resultsets the SQL query will be executed only after | ||||
| 1635 | the resultset is accessed via L</next> or L</all>. That would return | ||||
| 1636 | the same single value obtainable via L</count>. | ||||
| 1637 | |||||
| 1638 | =cut | ||||
| 1639 | |||||
| 1640 | sub count_rs { | ||||
| 1641 | my $self = shift; | ||||
| 1642 | return $self->search(@_)->count_rs if @_; | ||||
| 1643 | |||||
| 1644 | # this may look like a lack of abstraction (count() does about the same) | ||||
| 1645 | # but in fact an _rs *must* use a subquery for the limits, as the | ||||
| 1646 | # software based limiting can not be ported if this $rs is to be used | ||||
| 1647 | # in a subquery itself (i.e. ->as_query) | ||||
| 1648 | if ($self->_has_resolved_attr (qw/collapse group_by offset rows/)) { | ||||
| 1649 | return $self->_count_subq_rs($self->{_attrs}); | ||||
| 1650 | } | ||||
| 1651 | else { | ||||
| 1652 | return $self->_count_rs($self->{_attrs}); | ||||
| 1653 | } | ||||
| 1654 | } | ||||
| 1655 | |||||
| 1656 | # | ||||
| 1657 | # returns a ResultSetColumn object tied to the count query | ||||
| 1658 | # | ||||
| 1659 | sub _count_rs { | ||||
| 1660 | my ($self, $attrs) = @_; | ||||
| 1661 | |||||
| 1662 | my $rsrc = $self->result_source; | ||||
| 1663 | |||||
| 1664 | my $tmp_attrs = { %$attrs }; | ||||
| 1665 | # take off any limits, record_filter is cdbi, and no point of ordering nor locking a count | ||||
| 1666 | delete @{$tmp_attrs}{qw/rows offset order_by record_filter for/}; | ||||
| 1667 | |||||
| 1668 | # overwrite the selector (supplied by the storage) | ||||
| 1669 | $rsrc->resultset_class->new($rsrc, { | ||||
| 1670 | %$tmp_attrs, | ||||
| 1671 | select => $rsrc->storage->_count_select ($rsrc, $attrs), | ||||
| 1672 | as => 'count', | ||||
| 1673 | })->get_column ('count'); | ||||
| 1674 | } | ||||
| 1675 | |||||
| 1676 | # | ||||
| 1677 | # same as above but uses a subquery | ||||
| 1678 | # | ||||
| 1679 | sub _count_subq_rs { | ||||
| 1680 | my ($self, $attrs) = @_; | ||||
| 1681 | |||||
| 1682 | my $rsrc = $self->result_source; | ||||
| 1683 | |||||
| 1684 | my $sub_attrs = { %$attrs }; | ||||
| 1685 | # extra selectors do not go in the subquery and there is no point of ordering it, nor locking it | ||||
| 1686 | delete @{$sub_attrs}{qw/collapse columns as select order_by for/}; | ||||
| 1687 | |||||
| 1688 | # if we multi-prefetch we group_by something unique, as this is what we would | ||||
| 1689 | # get out of the rs via ->next/->all. We *DO WANT* to clobber old group_by regardless | ||||
| 1690 | if ( $attrs->{collapse} ) { | ||||
| 1691 | $sub_attrs->{group_by} = [ map { "$attrs->{alias}.$_" } @{ | ||||
| 1692 | $rsrc->_identifying_column_set || $self->throw_exception( | ||||
| 1693 | 'Unable to construct a unique group_by criteria properly collapsing the ' | ||||
| 1694 | . 'has_many prefetch before count()' | ||||
| 1695 | ); | ||||
| 1696 | } ] | ||||
| 1697 | } | ||||
| 1698 | |||||
| 1699 | # Calculate subquery selector | ||||
| 1700 | if (my $g = $sub_attrs->{group_by}) { | ||||
| 1701 | |||||
| 1702 | my $sql_maker = $rsrc->storage->sql_maker; | ||||
| 1703 | |||||
| 1704 | # necessary as the group_by may refer to aliased functions | ||||
| 1705 | my $sel_index; | ||||
| 1706 | for my $sel (@{$attrs->{select}}) { | ||||
| 1707 | $sel_index->{$sel->{-as}} = $sel | ||||
| 1708 | if (ref $sel eq 'HASH' and $sel->{-as}); | ||||
| 1709 | } | ||||
| 1710 | |||||
| 1711 | # anything from the original select mentioned on the group-by needs to make it to the inner selector | ||||
| 1712 | # also look for named aggregates referred in the having clause | ||||
| 1713 | # having often contains scalarrefs - thus parse it out entirely | ||||
| 1714 | my @parts = @$g; | ||||
| 1715 | if ($attrs->{having}) { | ||||
| 1716 | local $sql_maker->{having_bind}; | ||||
| 1717 | local $sql_maker->{quote_char} = $sql_maker->{quote_char}; | ||||
| 1718 | local $sql_maker->{name_sep} = $sql_maker->{name_sep}; | ||||
| 1719 | unless (defined $sql_maker->{quote_char} and length $sql_maker->{quote_char}) { | ||||
| 1720 | $sql_maker->{quote_char} = [ "\x00", "\xFF" ]; | ||||
| 1721 | # if we don't unset it we screw up retarded but unfortunately working | ||||
| 1722 | # 'MAX(foo.bar)' => { '>', 3 } | ||||
| 1723 | $sql_maker->{name_sep} = ''; | ||||
| 1724 | } | ||||
| 1725 | |||||
| 1726 | my ($lquote, $rquote, $sep) = map { quotemeta $_ } ($sql_maker->_quote_chars, $sql_maker->name_sep); | ||||
| 1727 | |||||
| 1728 | my $having_sql = $sql_maker->_parse_rs_attrs ({ having => $attrs->{having} }); | ||||
| 1729 | my %seen_having; | ||||
| 1730 | |||||
| 1731 | # search for both a proper quoted qualified string, for a naive unquoted scalarref | ||||
| 1732 | # and if all fails for an utterly naive quoted scalar-with-function | ||||
| 1733 | while ($having_sql =~ / | ||||
| 1734 | $rquote $sep $lquote (.+?) $rquote | ||||
| 1735 | | | ||||
| 1736 | [\s,] \w+ \. (\w+) [\s,] | ||||
| 1737 | | | ||||
| 1738 | [\s,] $lquote (.+?) $rquote [\s,] | ||||
| 1739 | /gx) { | ||||
| 1740 | my $part = $1 || $2 || $3; # one of them matched if we got here | ||||
| 1741 | unless ($seen_having{$part}++) { | ||||
| 1742 | push @parts, $part; | ||||
| 1743 | } | ||||
| 1744 | } | ||||
| 1745 | } | ||||
| 1746 | |||||
| 1747 | for (@parts) { | ||||
| 1748 | my $colpiece = $sel_index->{$_} || $_; | ||||
| 1749 | |||||
| 1750 | # unqualify join-based group_by's. Arcane but possible query | ||||
| 1751 | # also horrible horrible hack to alias a column (not a func.) | ||||
| 1752 | # (probably need to introduce SQLA syntax) | ||||
| 1753 | if ($colpiece =~ /\./ && $colpiece !~ /^$attrs->{alias}\./) { | ||||
| 1754 | my $as = $colpiece; | ||||
| 1755 | $as =~ s/\./__/; | ||||
| 1756 | $colpiece = \ sprintf ('%s AS %s', map { $sql_maker->_quote ($_) } ($colpiece, $as) ); | ||||
| 1757 | } | ||||
| 1758 | push @{$sub_attrs->{select}}, $colpiece; | ||||
| 1759 | } | ||||
| 1760 | } | ||||
| 1761 | else { | ||||
| 1762 | my @pcols = map { "$attrs->{alias}.$_" } ($rsrc->primary_columns); | ||||
| 1763 | $sub_attrs->{select} = @pcols ? \@pcols : [ 1 ]; | ||||
| 1764 | } | ||||
| 1765 | |||||
| 1766 | return $rsrc->resultset_class | ||||
| 1767 | ->new ($rsrc, $sub_attrs) | ||||
| 1768 | ->as_subselect_rs | ||||
| 1769 | ->search ({}, { columns => { count => $rsrc->storage->_count_select ($rsrc, $attrs) } }) | ||||
| 1770 | ->get_column ('count'); | ||||
| 1771 | } | ||||
| 1772 | |||||
| 1773 | |||||
| 1774 | =head2 count_literal | ||||
| 1775 | |||||
| 1776 | B<CAVEAT>: C<count_literal> is provided for Class::DBI compatibility and | ||||
| 1777 | should only be used in that context. See L</search_literal> for further info. | ||||
| 1778 | |||||
| 1779 | =over 4 | ||||
| 1780 | |||||
| 1781 | =item Arguments: $sql_fragment, @standalone_bind_values | ||||
| 1782 | |||||
| 1783 | =item Return Value: $count | ||||
| 1784 | |||||
| 1785 | =back | ||||
| 1786 | |||||
| 1787 | Counts the results in a literal query. Equivalent to calling L</search_literal> | ||||
| 1788 | with the passed arguments, then L</count>. | ||||
| 1789 | |||||
| 1790 | =cut | ||||
| 1791 | |||||
| 1792 | sub count_literal { shift->search_literal(@_)->count; } | ||||
| 1793 | |||||
| 1794 | =head2 all | ||||
| 1795 | |||||
| 1796 | =over 4 | ||||
| 1797 | |||||
| 1798 | =item Arguments: none | ||||
| 1799 | |||||
| 1800 | =item Return Value: L<@result_objs|DBIx::Class::Manual::ResultClass> | ||||
| 1801 | |||||
| 1802 | =back | ||||
| 1803 | |||||
| 1804 | Returns all elements in the resultset. | ||||
| 1805 | |||||
| 1806 | =cut | ||||
| 1807 | |||||
| 1808 | sub all { | ||||
| 1809 | my $self = shift; | ||||
| 1810 | if(@_) { | ||||
| 1811 | $self->throw_exception("all() doesn't take any arguments, you probably wanted ->search(...)->all()"); | ||||
| 1812 | } | ||||
| 1813 | |||||
| 1814 | delete @{$self}{qw/_stashed_rows _stashed_results/}; | ||||
| 1815 | |||||
| 1816 | if (my $c = $self->get_cache) { | ||||
| 1817 | return @$c; | ||||
| 1818 | } | ||||
| 1819 | |||||
| 1820 | $self->cursor->reset; | ||||
| 1821 | |||||
| 1822 | my $objs = $self->_construct_results('fetch_all') || []; | ||||
| 1823 | |||||
| 1824 | $self->set_cache($objs) if $self->{attrs}{cache}; | ||||
| 1825 | |||||
| 1826 | return @$objs; | ||||
| 1827 | } | ||||
| 1828 | |||||
| 1829 | =head2 reset | ||||
| 1830 | |||||
| 1831 | =over 4 | ||||
| 1832 | |||||
| 1833 | =item Arguments: none | ||||
| 1834 | |||||
| 1835 | =item Return Value: $self | ||||
| 1836 | |||||
| 1837 | =back | ||||
| 1838 | |||||
| 1839 | Resets the resultset's cursor, so you can iterate through the elements again. | ||||
| 1840 | Implicitly resets the storage cursor, so a subsequent L</next> will trigger | ||||
| 1841 | another query. | ||||
| 1842 | |||||
| 1843 | =cut | ||||
| 1844 | |||||
| 1845 | sub reset { | ||||
| 1846 | my ($self) = @_; | ||||
| 1847 | |||||
| 1848 | delete @{$self}{qw/_stashed_rows _stashed_results/}; | ||||
| 1849 | $self->{all_cache_position} = 0; | ||||
| 1850 | $self->cursor->reset; | ||||
| 1851 | return $self; | ||||
| 1852 | } | ||||
| 1853 | |||||
| 1854 | =head2 first | ||||
| 1855 | |||||
| 1856 | =over 4 | ||||
| 1857 | |||||
| 1858 | =item Arguments: none | ||||
| 1859 | |||||
| 1860 | =item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | undef | ||||
| 1861 | |||||
| 1862 | =back | ||||
| 1863 | |||||
| 1864 | L<Resets|/reset> the resultset (causing a fresh query to storage) and returns | ||||
| 1865 | an object for the first result (or C<undef> if the resultset is empty). | ||||
| 1866 | |||||
| 1867 | =cut | ||||
| 1868 | |||||
| 1869 | sub first { | ||||
| 1870 | return $_[0]->reset->next; | ||||
| 1871 | } | ||||
| 1872 | |||||
| 1873 | |||||
| 1874 | # _rs_update_delete | ||||
| 1875 | # | ||||
| 1876 | # Determines whether and what type of subquery is required for the $rs operation. | ||||
| 1877 | # If grouping is necessary either supplies its own, or verifies the current one | ||||
| 1878 | # After all is done delegates to the proper storage method. | ||||
| 1879 | |||||
| 1880 | sub _rs_update_delete { | ||||
| 1881 | my ($self, $op, $values) = @_; | ||||
| 1882 | |||||
| 1883 | my $rsrc = $self->result_source; | ||||
| 1884 | my $storage = $rsrc->schema->storage; | ||||
| 1885 | |||||
| 1886 | my $attrs = { %{$self->_resolved_attrs} }; | ||||
| 1887 | |||||
| 1888 | my $join_classifications; | ||||
| 1889 | my ($existing_group_by) = delete @{$attrs}{qw(group_by _grouped_by_distinct)}; | ||||
| 1890 | |||||
| 1891 | # do we need a subquery for any reason? | ||||
| 1892 | my $needs_subq = ( | ||||
| 1893 | defined $existing_group_by | ||||
| 1894 | or | ||||
| 1895 | ref($attrs->{from}) ne 'ARRAY' | ||||
| 1896 | |||||
| 1897 | or | ||||
| 1898 | $self->_has_resolved_attr(qw/rows offset/) | ||||
| 1899 | |||||
| 1900 | ); | ||||
| 1901 | |||||
| 1902 | # simplify the joinmap, so we can further decide if a subq is necessary | ||||
| 1903 | if (!$needs_subq and @{$attrs->{from}} > 1) { | ||||
| 1904 | |||||
| 1905 | ($attrs->{from}, $join_classifications) = | ||||
| 1906 | $storage->_prune_unused_joins ($attrs); | ||||
| 1907 | |||||
| 1908 | # any non-pruneable non-local restricting joins imply subq | ||||
| 1909 | $needs_subq = defined List::Util::first { $_ ne $attrs->{alias} } keys %{ $join_classifications->{restricting} || {} }; | ||||
| 1910 | } | ||||
| 1911 | |||||
| 1912 | # check if the head is composite (by now all joins are thrown out unless $needs_subq) | ||||
| 1913 | $needs_subq ||= ( | ||||
| 1914 | (ref $attrs->{from}[0]) ne 'HASH' | ||||
| 1915 | or | ||||
| 1916 | ref $attrs->{from}[0]{ $attrs->{from}[0]{-alias} } | ||||
| 1917 | ); | ||||
| 1918 | |||||
| 1919 | my ($cond, $guard); | ||||
| 1920 | # do we need anything like a subquery? | ||||
| 1921 | if (! $needs_subq) { | ||||
| 1922 | # Most databases do not allow aliasing of tables in UPDATE/DELETE. Thus | ||||
| 1923 | # a condition containing 'me' or other table prefixes will not work | ||||
| 1924 | # at all. Tell SQLMaker to dequalify idents via a gross hack. | ||||
| 1925 | $cond = do { | ||||
| 1926 | my $sqla = $rsrc->storage->sql_maker; | ||||
| 1927 | local $sqla->{_dequalify_idents} = 1; | ||||
| 1928 | \[ $sqla->_recurse_where($self->{cond}) ]; | ||||
| 1929 | }; | ||||
| 1930 | } | ||||
| 1931 | else { | ||||
| 1932 | # we got this far - means it is time to wrap a subquery | ||||
| 1933 | my $idcols = $rsrc->_identifying_column_set || $self->throw_exception( | ||||
| 1934 | sprintf( | ||||
| 1935 | "Unable to perform complex resultset %s() without an identifying set of columns on source '%s'", | ||||
| 1936 | $op, | ||||
| 1937 | $rsrc->source_name, | ||||
| 1938 | ) | ||||
| 1939 | ); | ||||
| 1940 | |||||
| 1941 | # make a new $rs selecting only the PKs (that's all we really need for the subq) | ||||
| 1942 | delete $attrs->{$_} for qw/select as collapse/; | ||||
| 1943 | $attrs->{columns} = [ map { "$attrs->{alias}.$_" } @$idcols ]; | ||||
| 1944 | |||||
| 1945 | # this will be consumed by the pruner waaaaay down the stack | ||||
| 1946 | $attrs->{_force_prune_multiplying_joins} = 1; | ||||
| 1947 | |||||
| 1948 | my $subrs = (ref $self)->new($rsrc, $attrs); | ||||
| 1949 | |||||
| 1950 | if (@$idcols == 1) { | ||||
| 1951 | $cond = { $idcols->[0] => { -in => $subrs->as_query } }; | ||||
| 1952 | } | ||||
| 1953 | elsif ($storage->_use_multicolumn_in) { | ||||
| 1954 | # no syntax for calling this properly yet | ||||
| 1955 | # !!! EXPERIMENTAL API !!! WILL CHANGE !!! | ||||
| 1956 | $cond = $storage->sql_maker->_where_op_multicolumn_in ( | ||||
| 1957 | $idcols, # how do I convey a list of idents...? can binds reside on lhs? | ||||
| 1958 | $subrs->as_query | ||||
| 1959 | ), | ||||
| 1960 | } | ||||
| 1961 | else { | ||||
| 1962 | # if all else fails - get all primary keys and operate over a ORed set | ||||
| 1963 | # wrap in a transaction for consistency | ||||
| 1964 | # this is where the group_by/multiplication starts to matter | ||||
| 1965 | if ( | ||||
| 1966 | $existing_group_by | ||||
| 1967 | or | ||||
| 1968 | keys %{ $join_classifications->{multiplying} || {} } | ||||
| 1969 | |||||
| 1970 | |||||
| 1971 | ) { | ||||
| 1972 | # make sure if there is a supplied group_by it matches the columns compiled above | ||||
| 1973 | # perfectly. Anything else can not be sanely executed on most databases so croak | ||||
| 1974 | # right then and there | ||||
| 1975 | if ($existing_group_by) { | ||||
| 1976 | my @current_group_by = map | ||||
| 1977 | { $_ =~ /\./ ? $_ : "$attrs->{alias}.$_" } | ||||
| 1978 | @$existing_group_by | ||||
| 1979 | ; | ||||
| 1980 | |||||
| 1981 | if ( | ||||
| 1982 | join ("\x00", sort @current_group_by) | ||||
| 1983 | ne | ||||
| 1984 | join ("\x00", sort @{$attrs->{columns}} ) | ||||
| 1985 | ) { | ||||
| 1986 | $self->throw_exception ( | ||||
| 1987 | "You have just attempted a $op operation on a resultset which does group_by" | ||||
| 1988 | . ' on columns other than the primary keys, while DBIC internally needs to retrieve' | ||||
| 1989 | . ' the primary keys in a subselect. All sane RDBMS engines do not support this' | ||||
| 1990 | . ' kind of queries. Please retry the operation with a modified group_by or' | ||||
| 1991 | . ' without using one at all.' | ||||
| 1992 | ); | ||||
| 1993 | } | ||||
| 1994 | } | ||||
| 1995 | |||||
| 1996 | $subrs = $subrs->search({}, { group_by => $attrs->{columns} }); | ||||
| 1997 | } | ||||
| 1998 | |||||
| 1999 | $guard = $storage->txn_scope_guard; | ||||
| 2000 | |||||
| 2001 | for my $row ($subrs->cursor->all) { | ||||
| 2002 | push @$cond, { map | ||||
| 2003 | { $idcols->[$_] => $row->[$_] } | ||||
| 2004 | (0 .. $#$idcols) | ||||
| 2005 | }; | ||||
| 2006 | } | ||||
| 2007 | } | ||||
| 2008 | } | ||||
| 2009 | |||||
| 2010 | my $res = $cond ? $storage->$op ( | ||||
| 2011 | $rsrc, | ||||
| 2012 | $op eq 'update' ? $values : (), | ||||
| 2013 | $cond, | ||||
| 2014 | ) : '0E0'; | ||||
| 2015 | |||||
| 2016 | $guard->commit if $guard; | ||||
| 2017 | |||||
| 2018 | return $res; | ||||
| 2019 | } | ||||
| 2020 | |||||
| 2021 | =head2 update | ||||
| 2022 | |||||
| 2023 | =over 4 | ||||
| 2024 | |||||
| 2025 | =item Arguments: \%values | ||||
| 2026 | |||||
| 2027 | =item Return Value: $underlying_storage_rv | ||||
| 2028 | |||||
| 2029 | =back | ||||
| 2030 | |||||
| 2031 | Sets the specified columns in the resultset to the supplied values in a | ||||
| 2032 | single query. Note that this will not run any accessor/set_column/update | ||||
| 2033 | triggers, nor will it update any result object instances derived from this | ||||
| 2034 | resultset (this includes the contents of the L<resultset cache|/set_cache> | ||||
| 2035 | if any). See L</update_all> if you need to execute any on-update | ||||
| 2036 | triggers or cascades defined either by you or a | ||||
| 2037 | L<result component|DBIx::Class::Manual::Component/WHAT IS A COMPONENT>. | ||||
| 2038 | |||||
| 2039 | The return value is a pass through of what the underlying | ||||
| 2040 | storage backend returned, and may vary. See L<DBI/execute> for the most | ||||
| 2041 | common case. | ||||
| 2042 | |||||
| 2043 | =head3 CAVEAT | ||||
| 2044 | |||||
| 2045 | Note that L</update> does not process/deflate any of the values passed in. | ||||
| 2046 | This is unlike the corresponding L<DBIx::Class::Row/update>. The user must | ||||
| 2047 | ensure manually that any value passed to this method will stringify to | ||||
| 2048 | something the RDBMS knows how to deal with. A notable example is the | ||||
| 2049 | handling of L<DateTime> objects, for more info see: | ||||
| 2050 | L<DBIx::Class::Manual::Cookbook/Formatting DateTime objects in queries>. | ||||
| 2051 | |||||
| 2052 | =cut | ||||
| 2053 | |||||
| 2054 | sub update { | ||||
| 2055 | my ($self, $values) = @_; | ||||
| 2056 | $self->throw_exception('Values for update must be a hash') | ||||
| 2057 | unless ref $values eq 'HASH'; | ||||
| 2058 | |||||
| 2059 | return $self->_rs_update_delete ('update', $values); | ||||
| 2060 | } | ||||
| 2061 | |||||
| 2062 | =head2 update_all | ||||
| 2063 | |||||
| 2064 | =over 4 | ||||
| 2065 | |||||
| 2066 | =item Arguments: \%values | ||||
| 2067 | |||||
| 2068 | =item Return Value: 1 | ||||
| 2069 | |||||
| 2070 | =back | ||||
| 2071 | |||||
| 2072 | Fetches all objects and updates them one at a time via | ||||
| 2073 | L<DBIx::Class::Row/update>. Note that C<update_all> will run DBIC defined | ||||
| 2074 | triggers, while L</update> will not. | ||||
| 2075 | |||||
| 2076 | =cut | ||||
| 2077 | |||||
| 2078 | sub update_all { | ||||
| 2079 | my ($self, $values) = @_; | ||||
| 2080 | $self->throw_exception('Values for update_all must be a hash') | ||||
| 2081 | unless ref $values eq 'HASH'; | ||||
| 2082 | |||||
| 2083 | my $guard = $self->result_source->schema->txn_scope_guard; | ||||
| 2084 | $_->update({%$values}) for $self->all; # shallow copy - update will mangle it | ||||
| 2085 | $guard->commit; | ||||
| 2086 | return 1; | ||||
| 2087 | } | ||||
| 2088 | |||||
| 2089 | =head2 delete | ||||
| 2090 | |||||
| 2091 | =over 4 | ||||
| 2092 | |||||
| 2093 | =item Arguments: none | ||||
| 2094 | |||||
| 2095 | =item Return Value: $underlying_storage_rv | ||||
| 2096 | |||||
| 2097 | =back | ||||
| 2098 | |||||
| 2099 | Deletes the rows matching this resultset in a single query. Note that this | ||||
| 2100 | will not run any delete triggers, nor will it alter the | ||||
| 2101 | L<in_storage|DBIx::Class::Row/in_storage> status of any result object instances | ||||
| 2102 | derived from this resultset (this includes the contents of the | ||||
| 2103 | L<resultset cache|/set_cache> if any). See L</delete_all> if you need to | ||||
| 2104 | execute any on-delete triggers or cascades defined either by you or a | ||||
| 2105 | L<result component|DBIx::Class::Manual::Component/WHAT IS A COMPONENT>. | ||||
| 2106 | |||||
| 2107 | The return value is a pass through of what the underlying storage backend | ||||
| 2108 | returned, and may vary. See L<DBI/execute> for the most common case. | ||||
| 2109 | |||||
| 2110 | =cut | ||||
| 2111 | |||||
| 2112 | sub delete { | ||||
| 2113 | my $self = shift; | ||||
| 2114 | $self->throw_exception('delete does not accept any arguments') | ||||
| 2115 | if @_; | ||||
| 2116 | |||||
| 2117 | return $self->_rs_update_delete ('delete'); | ||||
| 2118 | } | ||||
| 2119 | |||||
| 2120 | =head2 delete_all | ||||
| 2121 | |||||
| 2122 | =over 4 | ||||
| 2123 | |||||
| 2124 | =item Arguments: none | ||||
| 2125 | |||||
| 2126 | =item Return Value: 1 | ||||
| 2127 | |||||
| 2128 | =back | ||||
| 2129 | |||||
| 2130 | Fetches all objects and deletes them one at a time via | ||||
| 2131 | L<DBIx::Class::Row/delete>. Note that C<delete_all> will run DBIC defined | ||||
| 2132 | triggers, while L</delete> will not. | ||||
| 2133 | |||||
| 2134 | =cut | ||||
| 2135 | |||||
| 2136 | sub delete_all { | ||||
| 2137 | my $self = shift; | ||||
| 2138 | $self->throw_exception('delete_all does not accept any arguments') | ||||
| 2139 | if @_; | ||||
| 2140 | |||||
| 2141 | my $guard = $self->result_source->schema->txn_scope_guard; | ||||
| 2142 | $_->delete for $self->all; | ||||
| 2143 | $guard->commit; | ||||
| 2144 | return 1; | ||||
| 2145 | } | ||||
| 2146 | |||||
| 2147 | =head2 populate | ||||
| 2148 | |||||
| 2149 | =over 4 | ||||
| 2150 | |||||
| 2151 | =item Arguments: [ \@column_list, \@row_values+ ] | [ \%col_data+ ] | ||||
| 2152 | |||||
| 2153 | =item Return Value: L<\@result_objects|DBIx::Class::Manual::ResultClass> (scalar context) | L<@result_objects|DBIx::Class::Manual::ResultClass> (list context) | ||||
| 2154 | |||||
| 2155 | =back | ||||
| 2156 | |||||
| 2157 | Accepts either an arrayref of hashrefs or alternatively an arrayref of | ||||
| 2158 | arrayrefs. | ||||
| 2159 | |||||
| 2160 | =over | ||||
| 2161 | |||||
| 2162 | =item NOTE | ||||
| 2163 | |||||
| 2164 | The context of this method call has an important effect on what is | ||||
| 2165 | submitted to storage. In void context data is fed directly to fastpath | ||||
| 2166 | insertion routines provided by the underlying storage (most often | ||||
| 2167 | L<DBI/execute_for_fetch>), bypassing the L<new|DBIx::Class::Row/new> and | ||||
| 2168 | L<insert|DBIx::Class::Row/insert> calls on the | ||||
| 2169 | L<Result|DBIx::Class::Manual::ResultClass> class, including any | ||||
| 2170 | augmentation of these methods provided by components. For example if you | ||||
| 2171 | are using something like L<DBIx::Class::UUIDColumns> to create primary | ||||
| 2172 | keys for you, you will find that your PKs are empty. In this case you | ||||
| 2173 | will have to explicitly force scalar or list context in order to create | ||||
| 2174 | those values. | ||||
| 2175 | |||||
| 2176 | =back | ||||
| 2177 | |||||
| 2178 | In non-void (scalar or list) context, this method is simply a wrapper | ||||
| 2179 | for L</create>. Depending on list or scalar context either a list of | ||||
| 2180 | L<Result|DBIx::Class::Manual::ResultClass> objects or an arrayref | ||||
| 2181 | containing these objects is returned. | ||||
| 2182 | |||||
| 2183 | When supplying data in "arrayref of arrayrefs" invocation style, the | ||||
| 2184 | first element should be a list of column names and each subsequent | ||||
| 2185 | element should be a data value in the earlier specified column order. | ||||
| 2186 | For example: | ||||
| 2187 | |||||
| 2188 | $schema->resultset("Artist")->populate([ | ||||
| 2189 | [ qw( artistid name ) ], | ||||
| 2190 | [ 100, 'A Formally Unknown Singer' ], | ||||
| 2191 | [ 101, 'A singer that jumped the shark two albums ago' ], | ||||
| 2192 | [ 102, 'An actually cool singer' ], | ||||
| 2193 | ]); | ||||
| 2194 | |||||
| 2195 | For the arrayref of hashrefs style each hashref should be a structure | ||||
| 2196 | suitable for passing to L</create>. Multi-create is also permitted with | ||||
| 2197 | this syntax. | ||||
| 2198 | |||||
| 2199 | $schema->resultset("Artist")->populate([ | ||||
| 2200 | { artistid => 4, name => 'Manufactured Crap', cds => [ | ||||
| 2201 | { title => 'My First CD', year => 2006 }, | ||||
| 2202 | { title => 'Yet More Tweeny-Pop crap', year => 2007 }, | ||||
| 2203 | ], | ||||
| 2204 | }, | ||||
| 2205 | { artistid => 5, name => 'Angsty-Whiny Girl', cds => [ | ||||
| 2206 | { title => 'My parents sold me to a record company', year => 2005 }, | ||||
| 2207 | { title => 'Why Am I So Ugly?', year => 2006 }, | ||||
| 2208 | { title => 'I Got Surgery and am now Popular', year => 2007 } | ||||
| 2209 | ], | ||||
| 2210 | }, | ||||
| 2211 | ]); | ||||
| 2212 | |||||
| 2213 | If you attempt a void-context multi-create as in the example above (each | ||||
| 2214 | Artist also has the related list of CDs), and B<do not> supply the | ||||
| 2215 | necessary autoinc foreign key information, this method will proxy to the | ||||
| 2216 | less efficient L</create>, and then throw the Result objects away. In this | ||||
| 2217 | case there are obviously no benefits to using this method over L</create>. | ||||
| 2218 | |||||
| 2219 | =cut | ||||
| 2220 | |||||
| 2221 | sub populate { | ||||
| 2222 | my $self = shift; | ||||
| 2223 | |||||
| 2224 | # this is naive and just a quick check | ||||
| 2225 | # the types will need to be checked more thoroughly when the | ||||
| 2226 | # multi-source populate gets added | ||||
| 2227 | my $data = ( | ||||
| 2228 | ref $_[0] eq 'ARRAY' | ||||
| 2229 | and | ||||
| 2230 | ( @{$_[0]} or return ) | ||||
| 2231 | and | ||||
| 2232 | ( ref $_[0][0] eq 'HASH' or ref $_[0][0] eq 'ARRAY' ) | ||||
| 2233 | and | ||||
| 2234 | $_[0] | ||||
| 2235 | ) or $self->throw_exception('Populate expects an arrayref of hashrefs or arrayref of arrayrefs'); | ||||
| 2236 | |||||
| 2237 | # FIXME - no cref handling | ||||
| 2238 | # At this point assume either hashes or arrays | ||||
| 2239 | |||||
| 2240 | if(defined wantarray) { | ||||
| 2241 | my (@results, $guard); | ||||
| 2242 | |||||
| 2243 | if (ref $data->[0] eq 'ARRAY') { | ||||
| 2244 | # column names only, nothing to do | ||||
| 2245 | return if @$data == 1; | ||||
| 2246 | |||||
| 2247 | $guard = $self->result_source->schema->storage->txn_scope_guard | ||||
| 2248 | if @$data > 2; | ||||
| 2249 | |||||
| 2250 | @results = map | ||||
| 2251 | { my $vals = $_; $self->new_result({ map { $data->[0][$_] => $vals->[$_] } 0..$#{$data->[0]} })->insert } | ||||
| 2252 | @{$data}[1 .. $#$data] | ||||
| 2253 | ; | ||||
| 2254 | } | ||||
| 2255 | else { | ||||
| 2256 | |||||
| 2257 | $guard = $self->result_source->schema->storage->txn_scope_guard | ||||
| 2258 | if @$data > 1; | ||||
| 2259 | |||||
| 2260 | @results = map { $self->new_result($_)->insert } @$data; | ||||
| 2261 | } | ||||
| 2262 | |||||
| 2263 | $guard->commit if $guard; | ||||
| 2264 | return wantarray ? @results : \@results; | ||||
| 2265 | } | ||||
| 2266 | |||||
| 2267 | # we have to deal with *possibly incomplete* related data | ||||
| 2268 | # this means we have to walk the data structure twice | ||||
| 2269 | # whether we want this or not | ||||
| 2270 | # jnap, I hate you ;) | ||||
| 2271 | my $rsrc = $self->result_source; | ||||
| 2272 | my $rel_info = { map { $_ => $rsrc->relationship_info($_) } $rsrc->relationships }; | ||||
| 2273 | |||||
| 2274 | my ($colinfo, $colnames, $slices_with_rels); | ||||
| 2275 | my $data_start = 0; | ||||
| 2276 | |||||
| 2277 | DATA_SLICE: | ||||
| 2278 | for my $i (0 .. $#$data) { | ||||
| 2279 | |||||
| 2280 | my $current_slice_seen_rel_infos; | ||||
| 2281 | |||||
| 2282 | ### Determine/Supplement collists | ||||
| 2283 | ### BEWARE - This is a hot piece of code, a lot of weird idioms were used | ||||
| 2284 | if( ref $data->[$i] eq 'ARRAY' ) { | ||||
| 2285 | |||||
| 2286 | # positional(!) explicit column list | ||||
| 2287 | if ($i == 0) { | ||||
| 2288 | # column names only, nothing to do | ||||
| 2289 | return if @$data == 1; | ||||
| 2290 | |||||
| 2291 | $colinfo->{$data->[0][$_]} = { pos => $_, name => $data->[0][$_] } and push @$colnames, $data->[0][$_] | ||||
| 2292 | for 0 .. $#{$data->[0]}; | ||||
| 2293 | |||||
| 2294 | $data_start = 1; | ||||
| 2295 | |||||
| 2296 | next DATA_SLICE; | ||||
| 2297 | } | ||||
| 2298 | else { | ||||
| 2299 | for (values %$colinfo) { | ||||
| 2300 | if ($_->{is_rel} ||= ( | ||||
| 2301 | $rel_info->{$_->{name}} | ||||
| 2302 | and | ||||
| 2303 | ( | ||||
| 2304 | ref $data->[$i][$_->{pos}] eq 'ARRAY' | ||||
| 2305 | or | ||||
| 2306 | ref $data->[$i][$_->{pos}] eq 'HASH' | ||||
| 2307 | or | ||||
| 2308 | ( defined blessed $data->[$i][$_->{pos}] and $data->[$i][$_->{pos}]->isa('DBIx::Class::Row') ) | ||||
| 2309 | ) | ||||
| 2310 | and | ||||
| 2311 | 1 | ||||
| 2312 | )) { | ||||
| 2313 | |||||
| 2314 | # moar sanity check... sigh | ||||
| 2315 | for ( ref $data->[$i][$_->{pos}] eq 'ARRAY' ? @{$data->[$i][$_->{pos}]} : $data->[$i][$_->{pos}] ) { | ||||
| 2316 | if ( defined blessed $_ and $_->isa('DBIx::Class::Row' ) ) { | ||||
| 2317 | carp_unique("Fast-path populate() with supplied related objects is not possible - falling back to regular create()"); | ||||
| 2318 | return my $throwaway = $self->populate(@_); | ||||
| 2319 | } | ||||
| 2320 | } | ||||
| 2321 | |||||
| 2322 | push @$current_slice_seen_rel_infos, $rel_info->{$_->{name}}; | ||||
| 2323 | } | ||||
| 2324 | } | ||||
| 2325 | } | ||||
| 2326 | |||||
| 2327 | if ($current_slice_seen_rel_infos) { | ||||
| 2328 | push @$slices_with_rels, { map { $colnames->[$_] => $data->[$i][$_] } 0 .. $#$colnames }; | ||||
| 2329 | |||||
| 2330 | # this is needed further down to decide whether or not to fallback to create() | ||||
| 2331 | $colinfo->{$colnames->[$_]}{seen_null} ||= ! defined $data->[$i][$_] | ||||
| 2332 | for 0 .. $#$colnames; | ||||
| 2333 | } | ||||
| 2334 | } | ||||
| 2335 | elsif( ref $data->[$i] eq 'HASH' ) { | ||||
| 2336 | |||||
| 2337 | for ( sort keys %{$data->[$i]} ) { | ||||
| 2338 | |||||
| 2339 | $colinfo->{$_} ||= do { | ||||
| 2340 | |||||
| 2341 | $self->throw_exception("Column '$_' must be present in supplied explicit column list") | ||||
| 2342 | if $data_start; # it will be 0 on AoH, 1 on AoA | ||||
| 2343 | |||||
| 2344 | push @$colnames, $_; | ||||
| 2345 | |||||
| 2346 | # RV | ||||
| 2347 | { pos => $#$colnames, name => $_ } | ||||
| 2348 | }; | ||||
| 2349 | |||||
| 2350 | if ($colinfo->{$_}{is_rel} ||= ( | ||||
| 2351 | $rel_info->{$_} | ||||
| 2352 | and | ||||
| 2353 | ( | ||||
| 2354 | ref $data->[$i]{$_} eq 'ARRAY' | ||||
| 2355 | or | ||||
| 2356 | ref $data->[$i]{$_} eq 'HASH' | ||||
| 2357 | or | ||||
| 2358 | ( defined blessed $data->[$i]{$_} and $data->[$i]{$_}->isa('DBIx::Class::Row') ) | ||||
| 2359 | ) | ||||
| 2360 | and | ||||
| 2361 | 1 | ||||
| 2362 | )) { | ||||
| 2363 | |||||
| 2364 | # moar sanity check... sigh | ||||
| 2365 | for ( ref $data->[$i]{$_} eq 'ARRAY' ? @{$data->[$i]{$_}} : $data->[$i]{$_} ) { | ||||
| 2366 | if ( defined blessed $_ and $_->isa('DBIx::Class::Row' ) ) { | ||||
| 2367 | carp_unique("Fast-path populate() with supplied related objects is not possible - falling back to regular create()"); | ||||
| 2368 | return my $throwaway = $self->populate(@_); | ||||
| 2369 | } | ||||
| 2370 | } | ||||
| 2371 | |||||
| 2372 | push @$current_slice_seen_rel_infos, $rel_info->{$_}; | ||||
| 2373 | } | ||||
| 2374 | } | ||||
| 2375 | |||||
| 2376 | if ($current_slice_seen_rel_infos) { | ||||
| 2377 | push @$slices_with_rels, $data->[$i]; | ||||
| 2378 | |||||
| 2379 | # this is needed further down to decide whether or not to fallback to create() | ||||
| 2380 | $colinfo->{$_}{seen_null} ||= ! defined $data->[$i]{$_} | ||||
| 2381 | for keys %{$data->[$i]}; | ||||
| 2382 | } | ||||
| 2383 | } | ||||
| 2384 | else { | ||||
| 2385 | $self->throw_exception('Unexpected populate() data structure member type: ' . ref $data->[$i] ); | ||||
| 2386 | } | ||||
| 2387 | |||||
| 2388 | if ( grep | ||||
| 2389 | { $_->{attrs}{is_depends_on} } | ||||
| 2390 | @{ $current_slice_seen_rel_infos || [] } | ||||
| 2391 | ) { | ||||
| 2392 | carp_unique("Fast-path populate() of belongs_to relationship data is not possible - falling back to regular create()"); | ||||
| 2393 | return my $throwaway = $self->populate(@_); | ||||
| 2394 | } | ||||
| 2395 | } | ||||
| 2396 | |||||
| 2397 | if( $slices_with_rels ) { | ||||
| 2398 | |||||
| 2399 | # need to exclude the rel "columns" | ||||
| 2400 | $colnames = [ grep { ! $colinfo->{$_}{is_rel} } @$colnames ]; | ||||
| 2401 | |||||
| 2402 | # extra sanity check - ensure the main source is in fact identifiable | ||||
| 2403 | # the localizing of nullability is insane, but oh well... the use-case is legit | ||||
| 2404 | my $ci = $rsrc->columns_info($colnames); | ||||
| 2405 | |||||
| 2406 | $ci->{$_} = { %{$ci->{$_}}, is_nullable => 0 } | ||||
| 2407 | for grep { ! $colinfo->{$_}{seen_null} } keys %$ci; | ||||
| 2408 | |||||
| 2409 | unless( $rsrc->_identifying_column_set($ci) ) { | ||||
| 2410 | carp_unique("Fast-path populate() of non-uniquely identifiable rows with related data is not possible - falling back to regular create()"); | ||||
| 2411 | return my $throwaway = $self->populate(@_); | ||||
| 2412 | } | ||||
| 2413 | } | ||||
| 2414 | |||||
| 2415 | ### inherit the data locked in the conditions of the resultset | ||||
| 2416 | my ($rs_data) = $self->_merge_with_rscond({}); | ||||
| 2417 | delete @{$rs_data}{@$colnames}; # passed-in stuff takes precedence | ||||
| 2418 | |||||
| 2419 | # if anything left - decompose rs_data | ||||
| 2420 | my $rs_data_vals; | ||||
| 2421 | if (keys %$rs_data) { | ||||
| 2422 | push @$rs_data_vals, $rs_data->{$_} | ||||
| 2423 | for sort keys %$rs_data; | ||||
| 2424 | } | ||||
| 2425 | |||||
| 2426 | ### start work | ||||
| 2427 | my $guard; | ||||
| 2428 | $guard = $rsrc->schema->storage->txn_scope_guard | ||||
| 2429 | if $slices_with_rels; | ||||
| 2430 | |||||
| 2431 | ### main source data | ||||
| 2432 | # FIXME - need to switch entirely to a coderef-based thing, | ||||
| 2433 | # so that large sets aren't copied several times... I think | ||||
| 2434 | $rsrc->storage->_insert_bulk( | ||||
| 2435 | $rsrc, | ||||
| 2436 | [ @$colnames, sort keys %$rs_data ], | ||||
| 2437 | [ map { | ||||
| 2438 | ref $data->[$_] eq 'ARRAY' | ||||
| 2439 | ? ( | ||||
| 2440 | $slices_with_rels ? [ @{$data->[$_]}[0..$#$colnames], @{$rs_data_vals||[]} ] # the collist changed | ||||
| 2441 | : $rs_data_vals ? [ @{$data->[$_]}, @$rs_data_vals ] | ||||
| 2442 | : $data->[$_] | ||||
| 2443 | ) | ||||
| 2444 | : [ @{$data->[$_]}{@$colnames}, @{$rs_data_vals||[]} ] | ||||
| 2445 | } $data_start .. $#$data ], | ||||
| 2446 | ); | ||||
| 2447 | |||||
| 2448 | ### do the children relationships | ||||
| 2449 | if ( $slices_with_rels ) { | ||||
| 2450 | my @rels = grep { $colinfo->{$_}{is_rel} } keys %$colinfo | ||||
| 2451 | or die 'wtf... please report a bug with DBIC_TRACE=1 output (stacktrace)'; | ||||
| 2452 | |||||
| 2453 | for my $sl (@$slices_with_rels) { | ||||
| 2454 | |||||
| 2455 | my ($main_proto, $main_proto_rs); | ||||
| 2456 | for my $rel (@rels) { | ||||
| 2457 | next unless defined $sl->{$rel}; | ||||
| 2458 | |||||
| 2459 | $main_proto ||= { | ||||
| 2460 | %$rs_data, | ||||
| 2461 | (map { $_ => $sl->{$_} } @$colnames), | ||||
| 2462 | }; | ||||
| 2463 | |||||
| 2464 | unless (defined $colinfo->{$rel}{rs}) { | ||||
| 2465 | |||||
| 2466 | $colinfo->{$rel}{rs} = $rsrc->related_source($rel)->resultset; | ||||
| 2467 | |||||
| 2468 | $colinfo->{$rel}{fk_map} = { reverse %{ $rsrc->_resolve_relationship_condition( | ||||
| 2469 | rel_name => $rel, | ||||
| 2470 | self_alias => "\xFE", # irrelevant | ||||
| 2471 | foreign_alias => "\xFF", # irrelevant | ||||
| 2472 | )->{identity_map} || {} } }; | ||||
| 2473 | |||||
| 2474 | } | ||||
| 2475 | |||||
| 2476 | $colinfo->{$rel}{rs}->search({ map # only so that we inherit them values properly, no actual search | ||||
| 2477 | { | ||||
| 2478 | $_ => { '=' => | ||||
| 2479 | ( $main_proto_rs ||= $rsrc->resultset->search($main_proto) ) | ||||
| 2480 | ->get_column( $colinfo->{$rel}{fk_map}{$_} ) | ||||
| 2481 | ->as_query | ||||
| 2482 | } | ||||
| 2483 | } | ||||
| 2484 | keys %{$colinfo->{$rel}{fk_map}} | ||||
| 2485 | })->populate( ref $sl->{$rel} eq 'ARRAY' ? $sl->{$rel} : [ $sl->{$rel} ] ); | ||||
| 2486 | |||||
| 2487 | 1; | ||||
| 2488 | } | ||||
| 2489 | } | ||||
| 2490 | } | ||||
| 2491 | |||||
| 2492 | $guard->commit if $guard; | ||||
| 2493 | } | ||||
| 2494 | |||||
| 2495 | =head2 pager | ||||
| 2496 | |||||
| 2497 | =over 4 | ||||
| 2498 | |||||
| 2499 | =item Arguments: none | ||||
| 2500 | |||||
| 2501 | =item Return Value: L<$pager|Data::Page> | ||||
| 2502 | |||||
| 2503 | =back | ||||
| 2504 | |||||
| 2505 | Returns a L<Data::Page> object for the current resultset. Only makes | ||||
| 2506 | sense for queries with a C<page> attribute. | ||||
| 2507 | |||||
| 2508 | To get the full count of entries for a paged resultset, call | ||||
| 2509 | C<total_entries> on the L<Data::Page> object. | ||||
| 2510 | |||||
| 2511 | =cut | ||||
| 2512 | |||||
| 2513 | sub pager { | ||||
| 2514 | my ($self) = @_; | ||||
| 2515 | |||||
| 2516 | return $self->{pager} if $self->{pager}; | ||||
| 2517 | |||||
| 2518 | my $attrs = $self->{attrs}; | ||||
| 2519 | if (!defined $attrs->{page}) { | ||||
| 2520 | $self->throw_exception("Can't create pager for non-paged rs"); | ||||
| 2521 | } | ||||
| 2522 | elsif ($attrs->{page} <= 0) { | ||||
| 2523 | $self->throw_exception('Invalid page number (page-numbers are 1-based)'); | ||||
| 2524 | } | ||||
| 2525 | $attrs->{rows} ||= 10; | ||||
| 2526 | |||||
| 2527 | # throw away the paging flags and re-run the count (possibly | ||||
| 2528 | # with a subselect) to get the real total count | ||||
| 2529 | my $count_attrs = { %$attrs }; | ||||
| 2530 | delete @{$count_attrs}{qw/rows offset page pager/}; | ||||
| 2531 | |||||
| 2532 | my $total_rs = (ref $self)->new($self->result_source, $count_attrs); | ||||
| 2533 | |||||
| 2534 | require DBIx::Class::ResultSet::Pager; | ||||
| 2535 | return $self->{pager} = DBIx::Class::ResultSet::Pager->new( | ||||
| 2536 | sub { $total_rs->count }, #lazy-get the total | ||||
| 2537 | $attrs->{rows}, | ||||
| 2538 | $self->{attrs}{page}, | ||||
| 2539 | ); | ||||
| 2540 | } | ||||
| 2541 | |||||
| 2542 | =head2 page | ||||
| 2543 | |||||
| 2544 | =over 4 | ||||
| 2545 | |||||
| 2546 | =item Arguments: $page_number | ||||
| 2547 | |||||
| 2548 | =item Return Value: L<$resultset|/search> | ||||
| 2549 | |||||
| 2550 | =back | ||||
| 2551 | |||||
| 2552 | Returns a resultset for the $page_number page of the resultset on which page | ||||
| 2553 | is called, where each page contains a number of rows equal to the 'rows' | ||||
| 2554 | attribute set on the resultset (10 by default). | ||||
| 2555 | |||||
| 2556 | =cut | ||||
| 2557 | |||||
| 2558 | sub page { | ||||
| 2559 | my ($self, $page) = @_; | ||||
| 2560 | return (ref $self)->new($self->result_source, { %{$self->{attrs}}, page => $page }); | ||||
| 2561 | } | ||||
| 2562 | |||||
| 2563 | =head2 new_result | ||||
| 2564 | |||||
| 2565 | =over 4 | ||||
| 2566 | |||||
| 2567 | =item Arguments: \%col_data | ||||
| 2568 | |||||
| 2569 | =item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | ||||
| 2570 | |||||
| 2571 | =back | ||||
| 2572 | |||||
| 2573 | Creates a new result object in the resultset's result class and returns | ||||
| 2574 | it. The row is not inserted into the database at this point, call | ||||
| 2575 | L<DBIx::Class::Row/insert> to do that. Calling L<DBIx::Class::Row/in_storage> | ||||
| 2576 | will tell you whether the result object has been inserted or not. | ||||
| 2577 | |||||
| 2578 | Passes the hashref of input on to L<DBIx::Class::Row/new>. | ||||
| 2579 | |||||
| 2580 | =cut | ||||
| 2581 | |||||
| 2582 | sub new_result { | ||||
| 2583 | my ($self, $values) = @_; | ||||
| 2584 | |||||
| 2585 | $self->throw_exception( "new_result takes only one argument - a hashref of values" ) | ||||
| 2586 | if @_ > 2; | ||||
| 2587 | |||||
| 2588 | $self->throw_exception( "Result object instantiation requires a hashref as argument" ) | ||||
| 2589 | unless (ref $values eq 'HASH'); | ||||
| 2590 | |||||
| 2591 | my ($merged_cond, $cols_from_relations) = $self->_merge_with_rscond($values); | ||||
| 2592 | |||||
| 2593 | my $new = $self->result_class->new({ | ||||
| 2594 | %$merged_cond, | ||||
| 2595 | ( @$cols_from_relations | ||||
| 2596 | ? (-cols_from_relations => $cols_from_relations) | ||||
| 2597 | : () | ||||
| 2598 | ), | ||||
| 2599 | -result_source => $self->result_source, # DO NOT REMOVE THIS, REQUIRED | ||||
| 2600 | }); | ||||
| 2601 | |||||
| 2602 | if ( | ||||
| 2603 | reftype($new) eq 'HASH' | ||||
| 2604 | and | ||||
| 2605 | ! keys %$new | ||||
| 2606 | and | ||||
| 2607 | blessed($new) | ||||
| 2608 | ) { | ||||
| 2609 | carp_unique (sprintf ( | ||||
| 2610 | "%s->new returned a blessed empty hashref - a strong indicator something is wrong with its inheritance chain", | ||||
| 2611 | $self->result_class, | ||||
| 2612 | )); | ||||
| 2613 | } | ||||
| 2614 | |||||
| 2615 | $new; | ||||
| 2616 | } | ||||
| 2617 | |||||
| 2618 | # _merge_with_rscond | ||||
| 2619 | # | ||||
| 2620 | # Takes a simple hash of K/V data and returns its copy merged with the | ||||
| 2621 | # condition already present on the resultset. Additionally returns an | ||||
| 2622 | # arrayref of value/condition names, which were inferred from related | ||||
| 2623 | # objects (this is needed for in-memory related objects) | ||||
| 2624 | # spent 82.5ms (36.3+46.1) within DBIx::Class::ResultSet::_merge_with_rscond which was called 3000 times, avg 27µs/call:
# 3000 times (36.3ms+46.1ms) by Koha::Objects::find at line 842, avg 27µs/call | ||||
| 2625 | 3000 | 1.28ms | my ($self, $data) = @_; | ||
| 2626 | |||||
| 2627 | 3000 | 668µs | my ($implied_data, @cols_from_relations); | ||
| 2628 | |||||
| 2629 | 3000 | 2.72ms | my $alias = $self->{attrs}{alias}; | ||
| 2630 | |||||
| 2631 | 3000 | 2.01ms | if (! defined $self->{cond}) { | ||
| 2632 | # just massage $data below | ||||
| 2633 | } | ||||
| 2634 | elsif ($self->{cond} eq UNRESOLVABLE_CONDITION) { | ||||
| 2635 | $implied_data = $self->{attrs}{related_objects}; # nothing might have been inserted yet | ||||
| 2636 | @cols_from_relations = keys %{ $implied_data || {} }; | ||||
| 2637 | } | ||||
| 2638 | else { | ||||
| 2639 | my $eqs = $self->result_source->schema->storage->_extract_fixed_condition_columns($self->{cond}, 'consider_nulls'); | ||||
| 2640 | $implied_data = { map { | ||||
| 2641 | ( ($eqs->{$_}||'') eq UNRESOLVABLE_CONDITION ) ? () : ( $_ => $eqs->{$_} ) | ||||
| 2642 | } keys %$eqs }; | ||||
| 2643 | } | ||||
| 2644 | |||||
| 2645 | return ( | ||||
| 2646 | 3000 | 6.96ms | 3000 | 46.1ms | { map # spent 46.1ms making 3000 calls to DBIx::Class::ResultSet::_remove_alias, avg 15µs/call |
| 2647 | 6000 | 24.7ms | { %{ $self->_remove_alias($_, $alias) } } | ||
| 2648 | # precedence must be given to passed values over values inherited from | ||||
| 2649 | # the cond, so the order here is important. | ||||
| 2650 | ( $implied_data||(), $data) | ||||
| 2651 | }, | ||||
| 2652 | \@cols_from_relations | ||||
| 2653 | ); | ||||
| 2654 | } | ||||
| 2655 | |||||
| 2656 | # _has_resolved_attr | ||||
| 2657 | # | ||||
| 2658 | # determines if the resultset defines at least one | ||||
| 2659 | # of the attributes supplied | ||||
| 2660 | # | ||||
| 2661 | # used to determine if a subquery is necessary | ||||
| 2662 | # | ||||
| 2663 | # supports some virtual attributes: | ||||
| 2664 | # -join | ||||
| 2665 | # This will scan for any joins being present on the resultset. | ||||
| 2666 | # It is not a mere key-search but a deep inspection of {from} | ||||
| 2667 | # | ||||
| 2668 | |||||
| 2669 | sub _has_resolved_attr { | ||||
| 2670 | my ($self, @attr_names) = @_; | ||||
| 2671 | |||||
| 2672 | my $attrs = $self->_resolved_attrs; | ||||
| 2673 | |||||
| 2674 | my %extra_checks; | ||||
| 2675 | |||||
| 2676 | for my $n (@attr_names) { | ||||
| 2677 | if (grep { $n eq $_ } (qw/-join/) ) { | ||||
| 2678 | $extra_checks{$n}++; | ||||
| 2679 | next; | ||||
| 2680 | } | ||||
| 2681 | |||||
| 2682 | my $attr = $attrs->{$n}; | ||||
| 2683 | |||||
| 2684 | next if not defined $attr; | ||||
| 2685 | |||||
| 2686 | if (ref $attr eq 'HASH') { | ||||
| 2687 | return 1 if keys %$attr; | ||||
| 2688 | } | ||||
| 2689 | elsif (ref $attr eq 'ARRAY') { | ||||
| 2690 | return 1 if @$attr; | ||||
| 2691 | } | ||||
| 2692 | else { | ||||
| 2693 | return 1 if $attr; | ||||
| 2694 | } | ||||
| 2695 | } | ||||
| 2696 | |||||
| 2697 | # a resolved join is expressed as a multi-level from | ||||
| 2698 | return 1 if ( | ||||
| 2699 | $extra_checks{-join} | ||||
| 2700 | and | ||||
| 2701 | ref $attrs->{from} eq 'ARRAY' | ||||
| 2702 | and | ||||
| 2703 | @{$attrs->{from}} > 1 | ||||
| 2704 | ); | ||||
| 2705 | |||||
| 2706 | return 0; | ||||
| 2707 | } | ||||
| 2708 | |||||
| 2709 | # _remove_alias | ||||
| 2710 | # | ||||
| 2711 | # Remove the specified alias from the specified query hash. A copy is made so | ||||
| 2712 | # the original query is not modified. | ||||
| 2713 | |||||
| 2714 | # spent 46.1ms (41.3+4.88) within DBIx::Class::ResultSet::_remove_alias which was called 3000 times, avg 15µs/call:
# 3000 times (41.3ms+4.88ms) by DBIx::Class::ResultSet::_merge_with_rscond at line 2646, avg 15µs/call | ||||
| 2715 | 3000 | 1.35ms | my ($self, $query, $alias) = @_; | ||
| 2716 | |||||
| 2717 | 3000 | 5.22ms | my %orig = %{ $query || {} }; | ||
| 2718 | 3000 | 718µs | my %unaliased; | ||
| 2719 | |||||
| 2720 | 3000 | 5.46ms | foreach my $key (keys %orig) { | ||
| 2721 | 3000 | 21.6ms | 3000 | 4.88ms | if ($key !~ /\./) { # spent 4.88ms making 3000 calls to DBIx::Class::ResultSet::CORE:match, avg 2µs/call |
| 2722 | 3000 | 3.16ms | $unaliased{$key} = $orig{$key}; | ||
| 2723 | 3000 | 1.32ms | next; | ||
| 2724 | } | ||||
| 2725 | $unaliased{$1} = $orig{$key} | ||||
| 2726 | if $key =~ m/^(?:\Q$alias\E\.)?([^.]+)$/; | ||||
| 2727 | } | ||||
| 2728 | |||||
| 2729 | 3000 | 14.2ms | return \%unaliased; | ||
| 2730 | } | ||||
| 2731 | |||||
| 2732 | =head2 as_query | ||||
| 2733 | |||||
| 2734 | =over 4 | ||||
| 2735 | |||||
| 2736 | =item Arguments: none | ||||
| 2737 | |||||
| 2738 | =item Return Value: \[ $sql, L<@bind_values|/DBIC BIND VALUES> ] | ||||
| 2739 | |||||
| 2740 | =back | ||||
| 2741 | |||||
| 2742 | Returns the SQL query and bind vars associated with the invocant. | ||||
| 2743 | |||||
| 2744 | This is generally used as the RHS for a subquery. | ||||
| 2745 | |||||
| 2746 | =cut | ||||
| 2747 | |||||
| 2748 | sub as_query { | ||||
| 2749 | my $self = shift; | ||||
| 2750 | |||||
| 2751 | my $attrs = { %{ $self->_resolved_attrs } }; | ||||
| 2752 | |||||
| 2753 | my $aq = $self->result_source->storage->_select_args_to_query ( | ||||
| 2754 | $attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs | ||||
| 2755 | ); | ||||
| 2756 | |||||
| 2757 | $aq; | ||||
| 2758 | } | ||||
| 2759 | |||||
| 2760 | =head2 find_or_new | ||||
| 2761 | |||||
| 2762 | =over 4 | ||||
| 2763 | |||||
| 2764 | =item Arguments: \%col_data, { key => $unique_constraint, L<%attrs|/ATTRIBUTES> }? | ||||
| 2765 | |||||
| 2766 | =item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | ||||
| 2767 | |||||
| 2768 | =back | ||||
| 2769 | |||||
| 2770 | my $artist = $schema->resultset('Artist')->find_or_new( | ||||
| 2771 | { artist => 'fred' }, { key => 'artists' }); | ||||
| 2772 | |||||
| 2773 | $cd->cd_to_producer->find_or_new({ producer => $producer }, | ||||
| 2774 | { key => 'primary' }); | ||||
| 2775 | |||||
| 2776 | Find an existing record from this resultset using L</find>. if none exists, | ||||
| 2777 | instantiate a new result object and return it. The object will not be saved | ||||
| 2778 | into your storage until you call L<DBIx::Class::Row/insert> on it. | ||||
| 2779 | |||||
| 2780 | You most likely want this method when looking for existing rows using a unique | ||||
| 2781 | constraint that is not the primary key, or looking for related rows. | ||||
| 2782 | |||||
| 2783 | If you want objects to be saved immediately, use L</find_or_create> instead. | ||||
| 2784 | |||||
| 2785 | B<Note>: Make sure to read the documentation of L</find> and understand the | ||||
| 2786 | significance of the C<key> attribute, as its lack may skew your search, and | ||||
| 2787 | subsequently result in spurious new objects. | ||||
| 2788 | |||||
| 2789 | B<Note>: Take care when using C<find_or_new> with a table having | ||||
| 2790 | columns with default values that you intend to be automatically | ||||
| 2791 | supplied by the database (e.g. an auto_increment primary key column). | ||||
| 2792 | In normal usage, the value of such columns should NOT be included at | ||||
| 2793 | all in the call to C<find_or_new>, even when set to C<undef>. | ||||
| 2794 | |||||
| 2795 | =cut | ||||
| 2796 | |||||
| 2797 | sub find_or_new { | ||||
| 2798 | my $self = shift; | ||||
| 2799 | my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {}); | ||||
| 2800 | my $hash = ref $_[0] eq 'HASH' ? shift : {@_}; | ||||
| 2801 | if (keys %$hash and my $row = $self->find($hash, $attrs) ) { | ||||
| 2802 | return $row; | ||||
| 2803 | } | ||||
| 2804 | return $self->new_result($hash); | ||||
| 2805 | } | ||||
| 2806 | |||||
| 2807 | =head2 create | ||||
| 2808 | |||||
| 2809 | =over 4 | ||||
| 2810 | |||||
| 2811 | =item Arguments: \%col_data | ||||
| 2812 | |||||
| 2813 | =item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | ||||
| 2814 | |||||
| 2815 | =back | ||||
| 2816 | |||||
| 2817 | Attempt to create a single new row or a row with multiple related rows | ||||
| 2818 | in the table represented by the resultset (and related tables). This | ||||
| 2819 | will not check for duplicate rows before inserting, use | ||||
| 2820 | L</find_or_create> to do that. | ||||
| 2821 | |||||
| 2822 | To create one row for this resultset, pass a hashref of key/value | ||||
| 2823 | pairs representing the columns of the table and the values you wish to | ||||
| 2824 | store. If the appropriate relationships are set up, foreign key fields | ||||
| 2825 | can also be passed an object representing the foreign row, and the | ||||
| 2826 | value will be set to its primary key. | ||||
| 2827 | |||||
| 2828 | To create related objects, pass a hashref of related-object column values | ||||
| 2829 | B<keyed on the relationship name>. If the relationship is of type C<multi> | ||||
| 2830 | (L<DBIx::Class::Relationship/has_many>) - pass an arrayref of hashrefs. | ||||
| 2831 | The process will correctly identify columns holding foreign keys, and will | ||||
| 2832 | transparently populate them from the keys of the corresponding relation. | ||||
| 2833 | This can be applied recursively, and will work correctly for a structure | ||||
| 2834 | with an arbitrary depth and width, as long as the relationships actually | ||||
| 2835 | exists and the correct column data has been supplied. | ||||
| 2836 | |||||
| 2837 | Instead of hashrefs of plain related data (key/value pairs), you may | ||||
| 2838 | also pass new or inserted objects. New objects (not inserted yet, see | ||||
| 2839 | L</new_result>), will be inserted into their appropriate tables. | ||||
| 2840 | |||||
| 2841 | Effectively a shortcut for C<< ->new_result(\%col_data)->insert >>. | ||||
| 2842 | |||||
| 2843 | Example of creating a new row. | ||||
| 2844 | |||||
| 2845 | $person_rs->create({ | ||||
| 2846 | name=>"Some Person", | ||||
| 2847 | email=>"somebody@someplace.com" | ||||
| 2848 | }); | ||||
| 2849 | |||||
| 2850 | Example of creating a new row and also creating rows in a related C<has_many> | ||||
| 2851 | or C<has_one> resultset. Note Arrayref. | ||||
| 2852 | |||||
| 2853 | $artist_rs->create( | ||||
| 2854 | { artistid => 4, name => 'Manufactured Crap', cds => [ | ||||
| 2855 | { title => 'My First CD', year => 2006 }, | ||||
| 2856 | { title => 'Yet More Tweeny-Pop crap', year => 2007 }, | ||||
| 2857 | ], | ||||
| 2858 | }, | ||||
| 2859 | ); | ||||
| 2860 | |||||
| 2861 | Example of creating a new row and also creating a row in a related | ||||
| 2862 | C<belongs_to> resultset. Note Hashref. | ||||
| 2863 | |||||
| 2864 | $cd_rs->create({ | ||||
| 2865 | title=>"Music for Silly Walks", | ||||
| 2866 | year=>2000, | ||||
| 2867 | artist => { | ||||
| 2868 | name=>"Silly Musician", | ||||
| 2869 | } | ||||
| 2870 | }); | ||||
| 2871 | |||||
| 2872 | =over | ||||
| 2873 | |||||
| 2874 | =item WARNING | ||||
| 2875 | |||||
| 2876 | When subclassing ResultSet never attempt to override this method. Since | ||||
| 2877 | it is a simple shortcut for C<< $self->new_result($attrs)->insert >>, a | ||||
| 2878 | lot of the internals simply never call it, so your override will be | ||||
| 2879 | bypassed more often than not. Override either L<DBIx::Class::Row/new> | ||||
| 2880 | or L<DBIx::Class::Row/insert> depending on how early in the | ||||
| 2881 | L</create> process you need to intervene. See also warning pertaining to | ||||
| 2882 | L</new>. | ||||
| 2883 | |||||
| 2884 | =back | ||||
| 2885 | |||||
| 2886 | =cut | ||||
| 2887 | |||||
| 2888 | sub create { | ||||
| 2889 | #my ($self, $col_data) = @_; | ||||
| 2890 | DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call; | ||||
| 2891 | return shift->new_result(shift)->insert; | ||||
| 2892 | } | ||||
| 2893 | |||||
| 2894 | =head2 find_or_create | ||||
| 2895 | |||||
| 2896 | =over 4 | ||||
| 2897 | |||||
| 2898 | =item Arguments: \%col_data, { key => $unique_constraint, L<%attrs|/ATTRIBUTES> }? | ||||
| 2899 | |||||
| 2900 | =item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | ||||
| 2901 | |||||
| 2902 | =back | ||||
| 2903 | |||||
| 2904 | $cd->cd_to_producer->find_or_create({ producer => $producer }, | ||||
| 2905 | { key => 'primary' }); | ||||
| 2906 | |||||
| 2907 | Tries to find a record based on its primary key or unique constraints; if none | ||||
| 2908 | is found, creates one and returns that instead. | ||||
| 2909 | |||||
| 2910 | my $cd = $schema->resultset('CD')->find_or_create({ | ||||
| 2911 | cdid => 5, | ||||
| 2912 | artist => 'Massive Attack', | ||||
| 2913 | title => 'Mezzanine', | ||||
| 2914 | year => 2005, | ||||
| 2915 | }); | ||||
| 2916 | |||||
| 2917 | Also takes an optional C<key> attribute, to search by a specific key or unique | ||||
| 2918 | constraint. For example: | ||||
| 2919 | |||||
| 2920 | my $cd = $schema->resultset('CD')->find_or_create( | ||||
| 2921 | { | ||||
| 2922 | artist => 'Massive Attack', | ||||
| 2923 | title => 'Mezzanine', | ||||
| 2924 | }, | ||||
| 2925 | { key => 'cd_artist_title' } | ||||
| 2926 | ); | ||||
| 2927 | |||||
| 2928 | B<Note>: Make sure to read the documentation of L</find> and understand the | ||||
| 2929 | significance of the C<key> attribute, as its lack may skew your search, and | ||||
| 2930 | subsequently result in spurious row creation. | ||||
| 2931 | |||||
| 2932 | B<Note>: Because find_or_create() reads from the database and then | ||||
| 2933 | possibly inserts based on the result, this method is subject to a race | ||||
| 2934 | condition. Another process could create a record in the table after | ||||
| 2935 | the find has completed and before the create has started. To avoid | ||||
| 2936 | this problem, use find_or_create() inside a transaction. | ||||
| 2937 | |||||
| 2938 | B<Note>: Take care when using C<find_or_create> with a table having | ||||
| 2939 | columns with default values that you intend to be automatically | ||||
| 2940 | supplied by the database (e.g. an auto_increment primary key column). | ||||
| 2941 | In normal usage, the value of such columns should NOT be included at | ||||
| 2942 | all in the call to C<find_or_create>, even when set to C<undef>. | ||||
| 2943 | |||||
| 2944 | See also L</find> and L</update_or_create>. For information on how to declare | ||||
| 2945 | unique constraints, see L<DBIx::Class::ResultSource/add_unique_constraint>. | ||||
| 2946 | |||||
| 2947 | If you need to know if an existing row was found or a new one created use | ||||
| 2948 | L</find_or_new> and L<DBIx::Class::Row/in_storage> instead. Don't forget | ||||
| 2949 | to call L<DBIx::Class::Row/insert> to save the newly created row to the | ||||
| 2950 | database! | ||||
| 2951 | |||||
| 2952 | my $cd = $schema->resultset('CD')->find_or_new({ | ||||
| 2953 | cdid => 5, | ||||
| 2954 | artist => 'Massive Attack', | ||||
| 2955 | title => 'Mezzanine', | ||||
| 2956 | year => 2005, | ||||
| 2957 | }); | ||||
| 2958 | |||||
| 2959 | if( !$cd->in_storage ) { | ||||
| 2960 | # do some stuff | ||||
| 2961 | $cd->insert; | ||||
| 2962 | } | ||||
| 2963 | |||||
| 2964 | =cut | ||||
| 2965 | |||||
| 2966 | sub find_or_create { | ||||
| 2967 | my $self = shift; | ||||
| 2968 | my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {}); | ||||
| 2969 | my $hash = ref $_[0] eq 'HASH' ? shift : {@_}; | ||||
| 2970 | if (keys %$hash and my $row = $self->find($hash, $attrs) ) { | ||||
| 2971 | return $row; | ||||
| 2972 | } | ||||
| 2973 | return $self->new_result($hash)->insert; | ||||
| 2974 | } | ||||
| 2975 | |||||
| 2976 | =head2 update_or_create | ||||
| 2977 | |||||
| 2978 | =over 4 | ||||
| 2979 | |||||
| 2980 | =item Arguments: \%col_data, { key => $unique_constraint, L<%attrs|/ATTRIBUTES> }? | ||||
| 2981 | |||||
| 2982 | =item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | ||||
| 2983 | |||||
| 2984 | =back | ||||
| 2985 | |||||
| 2986 | $resultset->update_or_create({ col => $val, ... }); | ||||
| 2987 | |||||
| 2988 | Like L</find_or_create>, but if a row is found it is immediately updated via | ||||
| 2989 | C<< $found_row->update (\%col_data) >>. | ||||
| 2990 | |||||
| 2991 | |||||
| 2992 | Takes an optional C<key> attribute to search on a specific unique constraint. | ||||
| 2993 | For example: | ||||
| 2994 | |||||
| 2995 | # In your application | ||||
| 2996 | my $cd = $schema->resultset('CD')->update_or_create( | ||||
| 2997 | { | ||||
| 2998 | artist => 'Massive Attack', | ||||
| 2999 | title => 'Mezzanine', | ||||
| 3000 | year => 1998, | ||||
| 3001 | }, | ||||
| 3002 | { key => 'cd_artist_title' } | ||||
| 3003 | ); | ||||
| 3004 | |||||
| 3005 | $cd->cd_to_producer->update_or_create({ | ||||
| 3006 | producer => $producer, | ||||
| 3007 | name => 'harry', | ||||
| 3008 | }, { | ||||
| 3009 | key => 'primary', | ||||
| 3010 | }); | ||||
| 3011 | |||||
| 3012 | B<Note>: Make sure to read the documentation of L</find> and understand the | ||||
| 3013 | significance of the C<key> attribute, as its lack may skew your search, and | ||||
| 3014 | subsequently result in spurious row creation. | ||||
| 3015 | |||||
| 3016 | B<Note>: Take care when using C<update_or_create> with a table having | ||||
| 3017 | columns with default values that you intend to be automatically | ||||
| 3018 | supplied by the database (e.g. an auto_increment primary key column). | ||||
| 3019 | In normal usage, the value of such columns should NOT be included at | ||||
| 3020 | all in the call to C<update_or_create>, even when set to C<undef>. | ||||
| 3021 | |||||
| 3022 | See also L</find> and L</find_or_create>. For information on how to declare | ||||
| 3023 | unique constraints, see L<DBIx::Class::ResultSource/add_unique_constraint>. | ||||
| 3024 | |||||
| 3025 | If you need to know if an existing row was updated or a new one created use | ||||
| 3026 | L</update_or_new> and L<DBIx::Class::Row/in_storage> instead. Don't forget | ||||
| 3027 | to call L<DBIx::Class::Row/insert> to save the newly created row to the | ||||
| 3028 | database! | ||||
| 3029 | |||||
| 3030 | =cut | ||||
| 3031 | |||||
| 3032 | sub update_or_create { | ||||
| 3033 | my $self = shift; | ||||
| 3034 | my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {}); | ||||
| 3035 | my $cond = ref $_[0] eq 'HASH' ? shift : {@_}; | ||||
| 3036 | |||||
| 3037 | my $row = $self->find($cond, $attrs); | ||||
| 3038 | if (defined $row) { | ||||
| 3039 | $row->update($cond); | ||||
| 3040 | return $row; | ||||
| 3041 | } | ||||
| 3042 | |||||
| 3043 | return $self->new_result($cond)->insert; | ||||
| 3044 | } | ||||
| 3045 | |||||
| 3046 | =head2 update_or_new | ||||
| 3047 | |||||
| 3048 | =over 4 | ||||
| 3049 | |||||
| 3050 | =item Arguments: \%col_data, { key => $unique_constraint, L<%attrs|/ATTRIBUTES> }? | ||||
| 3051 | |||||
| 3052 | =item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | ||||
| 3053 | |||||
| 3054 | =back | ||||
| 3055 | |||||
| 3056 | $resultset->update_or_new({ col => $val, ... }); | ||||
| 3057 | |||||
| 3058 | Like L</find_or_new> but if a row is found it is immediately updated via | ||||
| 3059 | C<< $found_row->update (\%col_data) >>. | ||||
| 3060 | |||||
| 3061 | For example: | ||||
| 3062 | |||||
| 3063 | # In your application | ||||
| 3064 | my $cd = $schema->resultset('CD')->update_or_new( | ||||
| 3065 | { | ||||
| 3066 | artist => 'Massive Attack', | ||||
| 3067 | title => 'Mezzanine', | ||||
| 3068 | year => 1998, | ||||
| 3069 | }, | ||||
| 3070 | { key => 'cd_artist_title' } | ||||
| 3071 | ); | ||||
| 3072 | |||||
| 3073 | if ($cd->in_storage) { | ||||
| 3074 | # the cd was updated | ||||
| 3075 | } | ||||
| 3076 | else { | ||||
| 3077 | # the cd is not yet in the database, let's insert it | ||||
| 3078 | $cd->insert; | ||||
| 3079 | } | ||||
| 3080 | |||||
| 3081 | B<Note>: Make sure to read the documentation of L</find> and understand the | ||||
| 3082 | significance of the C<key> attribute, as its lack may skew your search, and | ||||
| 3083 | subsequently result in spurious new objects. | ||||
| 3084 | |||||
| 3085 | B<Note>: Take care when using C<update_or_new> with a table having | ||||
| 3086 | columns with default values that you intend to be automatically | ||||
| 3087 | supplied by the database (e.g. an auto_increment primary key column). | ||||
| 3088 | In normal usage, the value of such columns should NOT be included at | ||||
| 3089 | all in the call to C<update_or_new>, even when set to C<undef>. | ||||
| 3090 | |||||
| 3091 | See also L</find>, L</find_or_create> and L</find_or_new>. | ||||
| 3092 | |||||
| 3093 | =cut | ||||
| 3094 | |||||
| 3095 | sub update_or_new { | ||||
| 3096 | my $self = shift; | ||||
| 3097 | my $attrs = ( @_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {} ); | ||||
| 3098 | my $cond = ref $_[0] eq 'HASH' ? shift : {@_}; | ||||
| 3099 | |||||
| 3100 | my $row = $self->find( $cond, $attrs ); | ||||
| 3101 | if ( defined $row ) { | ||||
| 3102 | $row->update($cond); | ||||
| 3103 | return $row; | ||||
| 3104 | } | ||||
| 3105 | |||||
| 3106 | return $self->new_result($cond); | ||||
| 3107 | } | ||||
| 3108 | |||||
| 3109 | =head2 get_cache | ||||
| 3110 | |||||
| 3111 | =over 4 | ||||
| 3112 | |||||
| 3113 | =item Arguments: none | ||||
| 3114 | |||||
| 3115 | =item Return Value: L<\@result_objs|DBIx::Class::Manual::ResultClass> | undef | ||||
| 3116 | |||||
| 3117 | =back | ||||
| 3118 | |||||
| 3119 | Gets the contents of the cache for the resultset, if the cache is set. | ||||
| 3120 | |||||
| 3121 | The cache is populated either by using the L</prefetch> attribute to | ||||
| 3122 | L</search> or by calling L</set_cache>. | ||||
| 3123 | |||||
| 3124 | =cut | ||||
| 3125 | |||||
| 3126 | # spent 9.94ms within DBIx::Class::ResultSet::get_cache which was called 6000 times, avg 2µs/call:
# 6000 times (9.94ms+0s) by DBIx::Class::ResultSet::result_class at line 1561, avg 2µs/call | ||||
| 3127 | 6000 | 24.3ms | shift->{all_cache}; | ||
| 3128 | } | ||||
| 3129 | |||||
| 3130 | =head2 set_cache | ||||
| 3131 | |||||
| 3132 | =over 4 | ||||
| 3133 | |||||
| 3134 | =item Arguments: L<\@result_objs|DBIx::Class::Manual::ResultClass> | ||||
| 3135 | |||||
| 3136 | =item Return Value: L<\@result_objs|DBIx::Class::Manual::ResultClass> | ||||
| 3137 | |||||
| 3138 | =back | ||||
| 3139 | |||||
| 3140 | Sets the contents of the cache for the resultset. Expects an arrayref | ||||
| 3141 | of objects of the same class as those produced by the resultset. Note that | ||||
| 3142 | if the cache is set, the resultset will return the cached objects rather | ||||
| 3143 | than re-querying the database even if the cache attr is not set. | ||||
| 3144 | |||||
| 3145 | The contents of the cache can also be populated by using the | ||||
| 3146 | L</prefetch> attribute to L</search>. | ||||
| 3147 | |||||
| 3148 | =cut | ||||
| 3149 | |||||
| 3150 | sub set_cache { | ||||
| 3151 | my ( $self, $data ) = @_; | ||||
| 3152 | $self->throw_exception("set_cache requires an arrayref") | ||||
| 3153 | if defined($data) && (ref $data ne 'ARRAY'); | ||||
| 3154 | $self->{all_cache} = $data; | ||||
| 3155 | } | ||||
| 3156 | |||||
| 3157 | =head2 clear_cache | ||||
| 3158 | |||||
| 3159 | =over 4 | ||||
| 3160 | |||||
| 3161 | =item Arguments: none | ||||
| 3162 | |||||
| 3163 | =item Return Value: undef | ||||
| 3164 | |||||
| 3165 | =back | ||||
| 3166 | |||||
| 3167 | Clears the cache for the resultset. | ||||
| 3168 | |||||
| 3169 | =cut | ||||
| 3170 | |||||
| 3171 | sub clear_cache { | ||||
| 3172 | shift->set_cache(undef); | ||||
| 3173 | } | ||||
| 3174 | |||||
| 3175 | =head2 is_paged | ||||
| 3176 | |||||
| 3177 | =over 4 | ||||
| 3178 | |||||
| 3179 | =item Arguments: none | ||||
| 3180 | |||||
| 3181 | =item Return Value: true, if the resultset has been paginated | ||||
| 3182 | |||||
| 3183 | =back | ||||
| 3184 | |||||
| 3185 | =cut | ||||
| 3186 | |||||
| 3187 | sub is_paged { | ||||
| 3188 | my ($self) = @_; | ||||
| 3189 | return !!$self->{attrs}{page}; | ||||
| 3190 | } | ||||
| 3191 | |||||
| 3192 | =head2 is_ordered | ||||
| 3193 | |||||
| 3194 | =over 4 | ||||
| 3195 | |||||
| 3196 | =item Arguments: none | ||||
| 3197 | |||||
| 3198 | =item Return Value: true, if the resultset has been ordered with C<order_by>. | ||||
| 3199 | |||||
| 3200 | =back | ||||
| 3201 | |||||
| 3202 | =cut | ||||
| 3203 | |||||
| 3204 | sub is_ordered { | ||||
| 3205 | my ($self) = @_; | ||||
| 3206 | return scalar $self->result_source->storage->_extract_order_criteria($self->{attrs}{order_by}); | ||||
| 3207 | } | ||||
| 3208 | |||||
| 3209 | =head2 related_resultset | ||||
| 3210 | |||||
| 3211 | =over 4 | ||||
| 3212 | |||||
| 3213 | =item Arguments: $rel_name | ||||
| 3214 | |||||
| 3215 | =item Return Value: L<$resultset|/search> | ||||
| 3216 | |||||
| 3217 | =back | ||||
| 3218 | |||||
| 3219 | Returns a related resultset for the supplied relationship name. | ||||
| 3220 | |||||
| 3221 | $artist_rs = $schema->resultset('CD')->related_resultset('Artist'); | ||||
| 3222 | |||||
| 3223 | =cut | ||||
| 3224 | |||||
| 3225 | sub related_resultset { | ||||
| 3226 | my ($self, $rel) = @_; | ||||
| 3227 | |||||
| 3228 | return $self->{related_resultsets}{$rel} | ||||
| 3229 | if defined $self->{related_resultsets}{$rel}; | ||||
| 3230 | |||||
| 3231 | return $self->{related_resultsets}{$rel} = do { | ||||
| 3232 | my $rsrc = $self->result_source; | ||||
| 3233 | my $rel_info = $rsrc->relationship_info($rel); | ||||
| 3234 | |||||
| 3235 | $self->throw_exception( | ||||
| 3236 | "search_related: result source '" . $rsrc->source_name . | ||||
| 3237 | "' has no such relationship $rel") | ||||
| 3238 | unless $rel_info; | ||||
| 3239 | |||||
| 3240 | my $attrs = $self->_chain_relationship($rel); | ||||
| 3241 | |||||
| 3242 | my $join_count = $attrs->{seen_join}{$rel}; | ||||
| 3243 | |||||
| 3244 | my $alias = $self->result_source->storage | ||||
| 3245 | ->relname_to_table_alias($rel, $join_count); | ||||
| 3246 | |||||
| 3247 | # since this is search_related, and we already slid the select window inwards | ||||
| 3248 | # (the select/as attrs were deleted in the beginning), we need to flip all | ||||
| 3249 | # left joins to inner, so we get the expected results | ||||
| 3250 | # read the comment on top of the actual function to see what this does | ||||
| 3251 | $attrs->{from} = $rsrc->schema->storage->_inner_join_to_node ($attrs->{from}, $alias); | ||||
| 3252 | |||||
| 3253 | |||||
| 3254 | #XXX - temp fix for result_class bug. There likely is a more elegant fix -groditi | ||||
| 3255 | delete @{$attrs}{qw(result_class alias)}; | ||||
| 3256 | |||||
| 3257 | my $rel_source = $rsrc->related_source($rel); | ||||
| 3258 | |||||
| 3259 | my $new = do { | ||||
| 3260 | |||||
| 3261 | # The reason we do this now instead of passing the alias to the | ||||
| 3262 | # search_rs below is that if you wrap/overload resultset on the | ||||
| 3263 | # source you need to know what alias it's -going- to have for things | ||||
| 3264 | # to work sanely (e.g. RestrictWithObject wants to be able to add | ||||
| 3265 | # extra query restrictions, and these may need to be $alias.) | ||||
| 3266 | |||||
| 3267 | my $rel_attrs = $rel_source->resultset_attributes; | ||||
| 3268 | local $rel_attrs->{alias} = $alias; | ||||
| 3269 | |||||
| 3270 | $rel_source->resultset | ||||
| 3271 | ->search_rs( | ||||
| 3272 | undef, { | ||||
| 3273 | %$attrs, | ||||
| 3274 | where => $attrs->{where}, | ||||
| 3275 | }); | ||||
| 3276 | }; | ||||
| 3277 | |||||
| 3278 | if (my $cache = $self->get_cache) { | ||||
| 3279 | my @related_cache = map | ||||
| 3280 | { $_->related_resultset($rel)->get_cache || () } | ||||
| 3281 | @$cache | ||||
| 3282 | ; | ||||
| 3283 | |||||
| 3284 | $new->set_cache([ map @$_, @related_cache ]) if @related_cache == @$cache; | ||||
| 3285 | } | ||||
| 3286 | |||||
| 3287 | $new; | ||||
| 3288 | }; | ||||
| 3289 | } | ||||
| 3290 | |||||
| 3291 | =head2 current_source_alias | ||||
| 3292 | |||||
| 3293 | =over 4 | ||||
| 3294 | |||||
| 3295 | =item Arguments: none | ||||
| 3296 | |||||
| 3297 | =item Return Value: $source_alias | ||||
| 3298 | |||||
| 3299 | =back | ||||
| 3300 | |||||
| 3301 | Returns the current table alias for the result source this resultset is built | ||||
| 3302 | on, that will be used in the SQL query. Usually it is C<me>. | ||||
| 3303 | |||||
| 3304 | Currently the source alias that refers to the result set returned by a | ||||
| 3305 | L</search>/L</find> family method depends on how you got to the resultset: it's | ||||
| 3306 | C<me> by default, but eg. L</search_related> aliases it to the related result | ||||
| 3307 | source name (and keeps C<me> referring to the original result set). The long | ||||
| 3308 | term goal is to make L<DBIx::Class> always alias the current resultset as C<me> | ||||
| 3309 | (and make this method unnecessary). | ||||
| 3310 | |||||
| 3311 | Thus it's currently necessary to use this method in predefined queries (see | ||||
| 3312 | L<DBIx::Class::Manual::Cookbook/Predefined searches>) when referring to the | ||||
| 3313 | source alias of the current result set: | ||||
| 3314 | |||||
| 3315 | # in a result set class | ||||
| 3316 | sub modified_by { | ||||
| 3317 | my ($self, $user) = @_; | ||||
| 3318 | |||||
| 3319 | my $me = $self->current_source_alias; | ||||
| 3320 | |||||
| 3321 | return $self->search({ | ||||
| 3322 | "$me.modified" => $user->id, | ||||
| 3323 | }); | ||||
| 3324 | } | ||||
| 3325 | |||||
| 3326 | =cut | ||||
| 3327 | |||||
| 3328 | sub current_source_alias { | ||||
| 3329 | return (shift->{attrs} || {})->{alias} || 'me'; | ||||
| 3330 | } | ||||
| 3331 | |||||
| 3332 | =head2 as_subselect_rs | ||||
| 3333 | |||||
| 3334 | =over 4 | ||||
| 3335 | |||||
| 3336 | =item Arguments: none | ||||
| 3337 | |||||
| 3338 | =item Return Value: L<$resultset|/search> | ||||
| 3339 | |||||
| 3340 | =back | ||||
| 3341 | |||||
| 3342 | Act as a barrier to SQL symbols. The resultset provided will be made into a | ||||
| 3343 | "virtual view" by including it as a subquery within the from clause. From this | ||||
| 3344 | point on, any joined tables are inaccessible to ->search on the resultset (as if | ||||
| 3345 | it were simply where-filtered without joins). For example: | ||||
| 3346 | |||||
| 3347 | my $rs = $schema->resultset('Bar')->search({'x.name' => 'abc'},{ join => 'x' }); | ||||
| 3348 | |||||
| 3349 | # 'x' now pollutes the query namespace | ||||
| 3350 | |||||
| 3351 | # So the following works as expected | ||||
| 3352 | my $ok_rs = $rs->search({'x.other' => 1}); | ||||
| 3353 | |||||
| 3354 | # But this doesn't: instead of finding a 'Bar' related to two x rows (abc and | ||||
| 3355 | # def) we look for one row with contradictory terms and join in another table | ||||
| 3356 | # (aliased 'x_2') which we never use | ||||
| 3357 | my $broken_rs = $rs->search({'x.name' => 'def'}); | ||||
| 3358 | |||||
| 3359 | my $rs2 = $rs->as_subselect_rs; | ||||
| 3360 | |||||
| 3361 | # doesn't work - 'x' is no longer accessible in $rs2, having been sealed away | ||||
| 3362 | my $not_joined_rs = $rs2->search({'x.other' => 1}); | ||||
| 3363 | |||||
| 3364 | # works as expected: finds a 'table' row related to two x rows (abc and def) | ||||
| 3365 | my $correctly_joined_rs = $rs2->search({'x.name' => 'def'}); | ||||
| 3366 | |||||
| 3367 | Another example of when one might use this would be to select a subset of | ||||
| 3368 | columns in a group by clause: | ||||
| 3369 | |||||
| 3370 | my $rs = $schema->resultset('Bar')->search(undef, { | ||||
| 3371 | group_by => [qw{ id foo_id baz_id }], | ||||
| 3372 | })->as_subselect_rs->search(undef, { | ||||
| 3373 | columns => [qw{ id foo_id }] | ||||
| 3374 | }); | ||||
| 3375 | |||||
| 3376 | In the above example normally columns would have to be equal to the group by, | ||||
| 3377 | but because we isolated the group by into a subselect the above works. | ||||
| 3378 | |||||
| 3379 | =cut | ||||
| 3380 | |||||
| 3381 | sub as_subselect_rs { | ||||
| 3382 | my $self = shift; | ||||
| 3383 | |||||
| 3384 | my $attrs = $self->_resolved_attrs; | ||||
| 3385 | |||||
| 3386 | my $fresh_rs = (ref $self)->new ( | ||||
| 3387 | $self->result_source | ||||
| 3388 | ); | ||||
| 3389 | |||||
| 3390 | # these pieces will be locked in the subquery | ||||
| 3391 | delete $fresh_rs->{cond}; | ||||
| 3392 | delete @{$fresh_rs->{attrs}}{qw/where bind/}; | ||||
| 3393 | |||||
| 3394 | return $fresh_rs->search( {}, { | ||||
| 3395 | from => [{ | ||||
| 3396 | $attrs->{alias} => $self->as_query, | ||||
| 3397 | -alias => $attrs->{alias}, | ||||
| 3398 | -rsrc => $self->result_source, | ||||
| 3399 | }], | ||||
| 3400 | alias => $attrs->{alias}, | ||||
| 3401 | }); | ||||
| 3402 | } | ||||
| 3403 | |||||
| 3404 | # This code is called by search_related, and makes sure there | ||||
| 3405 | # is clear separation between the joins before, during, and | ||||
| 3406 | # after the relationship. This information is needed later | ||||
| 3407 | # in order to properly resolve prefetch aliases (any alias | ||||
| 3408 | # with a relation_chain_depth less than the depth of the | ||||
| 3409 | # current prefetch is not considered) | ||||
| 3410 | # | ||||
| 3411 | # The increments happen twice per join. An even number means a | ||||
| 3412 | # relationship specified via a search_related, whereas an odd | ||||
| 3413 | # number indicates a join/prefetch added via attributes | ||||
| 3414 | # | ||||
| 3415 | # Also this code will wrap the current resultset (the one we | ||||
| 3416 | # chain to) in a subselect IFF it contains limiting attributes | ||||
| 3417 | sub _chain_relationship { | ||||
| 3418 | my ($self, $rel) = @_; | ||||
| 3419 | my $source = $self->result_source; | ||||
| 3420 | my $attrs = { %{$self->{attrs}||{}} }; | ||||
| 3421 | |||||
| 3422 | # we need to take the prefetch the attrs into account before we | ||||
| 3423 | # ->_resolve_join as otherwise they get lost - captainL | ||||
| 3424 | my $join = $self->_merge_joinpref_attr( $attrs->{join}, $attrs->{prefetch} ); | ||||
| 3425 | |||||
| 3426 | delete @{$attrs}{qw/join prefetch collapse group_by distinct _grouped_by_distinct select as columns +select +as +columns/}; | ||||
| 3427 | |||||
| 3428 | my $seen = { %{ (delete $attrs->{seen_join}) || {} } }; | ||||
| 3429 | |||||
| 3430 | my $from; | ||||
| 3431 | my @force_subq_attrs = qw/offset rows group_by having/; | ||||
| 3432 | |||||
| 3433 | if ( | ||||
| 3434 | ($attrs->{from} && ref $attrs->{from} ne 'ARRAY') | ||||
| 3435 | || | ||||
| 3436 | $self->_has_resolved_attr (@force_subq_attrs) | ||||
| 3437 | ) { | ||||
| 3438 | # Nuke the prefetch (if any) before the new $rs attrs | ||||
| 3439 | # are resolved (prefetch is useless - we are wrapping | ||||
| 3440 | # a subquery anyway). | ||||
| 3441 | my $rs_copy = $self->search; | ||||
| 3442 | $rs_copy->{attrs}{join} = $self->_merge_joinpref_attr ( | ||||
| 3443 | $rs_copy->{attrs}{join}, | ||||
| 3444 | delete $rs_copy->{attrs}{prefetch}, | ||||
| 3445 | ); | ||||
| 3446 | |||||
| 3447 | $from = [{ | ||||
| 3448 | -rsrc => $source, | ||||
| 3449 | -alias => $attrs->{alias}, | ||||
| 3450 | $attrs->{alias} => $rs_copy->as_query, | ||||
| 3451 | }]; | ||||
| 3452 | delete @{$attrs}{@force_subq_attrs, qw/where bind/}; | ||||
| 3453 | $seen->{-relation_chain_depth} = 0; | ||||
| 3454 | } | ||||
| 3455 | elsif ($attrs->{from}) { #shallow copy suffices | ||||
| 3456 | $from = [ @{$attrs->{from}} ]; | ||||
| 3457 | } | ||||
| 3458 | else { | ||||
| 3459 | $from = [{ | ||||
| 3460 | -rsrc => $source, | ||||
| 3461 | -alias => $attrs->{alias}, | ||||
| 3462 | $attrs->{alias} => $source->from, | ||||
| 3463 | }]; | ||||
| 3464 | } | ||||
| 3465 | |||||
| 3466 | my $jpath = ($seen->{-relation_chain_depth}) | ||||
| 3467 | ? $from->[-1][0]{-join_path} | ||||
| 3468 | : []; | ||||
| 3469 | |||||
| 3470 | my @requested_joins = $source->_resolve_join( | ||||
| 3471 | $join, | ||||
| 3472 | $attrs->{alias}, | ||||
| 3473 | $seen, | ||||
| 3474 | $jpath, | ||||
| 3475 | ); | ||||
| 3476 | |||||
| 3477 | push @$from, @requested_joins; | ||||
| 3478 | |||||
| 3479 | $seen->{-relation_chain_depth}++; | ||||
| 3480 | |||||
| 3481 | # if $self already had a join/prefetch specified on it, the requested | ||||
| 3482 | # $rel might very well be already included. What we do in this case | ||||
| 3483 | # is effectively a no-op (except that we bump up the chain_depth on | ||||
| 3484 | # the join in question so we could tell it *is* the search_related) | ||||
| 3485 | my $already_joined; | ||||
| 3486 | |||||
| 3487 | # we consider the last one thus reverse | ||||
| 3488 | for my $j (reverse @requested_joins) { | ||||
| 3489 | my ($last_j) = keys %{$j->[0]{-join_path}[-1]}; | ||||
| 3490 | if ($rel eq $last_j) { | ||||
| 3491 | $j->[0]{-relation_chain_depth}++; | ||||
| 3492 | $already_joined++; | ||||
| 3493 | last; | ||||
| 3494 | } | ||||
| 3495 | } | ||||
| 3496 | |||||
| 3497 | unless ($already_joined) { | ||||
| 3498 | push @$from, $source->_resolve_join( | ||||
| 3499 | $rel, | ||||
| 3500 | $attrs->{alias}, | ||||
| 3501 | $seen, | ||||
| 3502 | $jpath, | ||||
| 3503 | ); | ||||
| 3504 | } | ||||
| 3505 | |||||
| 3506 | $seen->{-relation_chain_depth}++; | ||||
| 3507 | |||||
| 3508 | return {%$attrs, from => $from, seen_join => $seen}; | ||||
| 3509 | } | ||||
| 3510 | |||||
| 3511 | # spent 1.37s (1.17+201ms) within DBIx::Class::ResultSet::_resolved_attrs which was called 9000 times, avg 152µs/call:
# 3000 times (1.15s+201ms) by Koha::Objects::find at line 904, avg 451µs/call
# 3000 times (9.97ms+0s) by DBIx::Class::ResultSet::_construct_results at line 1268, avg 3µs/call
# 3000 times (4.57ms+0s) by DBIx::Class::ResultSet::single at line 1081, avg 2µs/call | ||||
| 3512 | 9000 | 2.91ms | my $self = shift; | ||
| 3513 | 9000 | 35.4ms | return $self->{_attrs} if $self->{_attrs}; | ||
| 3514 | |||||
| 3515 | 3000 | 8.34ms | my $attrs = { %{ $self->{attrs} || {} } }; | ||
| 3516 | 3000 | 4.11ms | 1 | 500ns | my $source = $attrs->{result_source} = $self->result_source; # spent 500ns making 1 call to DBIx::Class::ResultSet::result_source |
| 3517 | 3000 | 1.62ms | my $alias = $attrs->{alias}; | ||
| 3518 | |||||
| 3519 | 3000 | 1.39ms | $self->throw_exception("Specifying distinct => 1 in conjunction with collapse => 1 is unsupported") | ||
| 3520 | if $attrs->{collapse} and $attrs->{distinct}; | ||||
| 3521 | |||||
| 3522 | # default selection list | ||||
| 3523 | $attrs->{columns} = [ $source->columns ] | ||||
| 3524 | 15000 | 50.2ms | 6000 | 20.5ms | unless List::Util::first { exists $attrs->{$_} } qw/columns cols select as/; # spent 12.6ms making 3000 calls to DBIx::Class::ResultSource::columns, avg 4µs/call
# spent 7.85ms making 3000 calls to List::Util::first, avg 3µs/call |
| 3525 | |||||
| 3526 | # merge selectors together | ||||
| 3527 | 3000 | 3.47ms | for (qw/columns select as/) { | ||
| 3528 | 9000 | 19.0ms | 3000 | 102ms | $attrs->{$_} = $self->_merge_attr($attrs->{$_}, delete $attrs->{"+$_"}) # spent 102ms making 3000 calls to DBIx::Class::ResultSet::_merge_attr, avg 34µs/call |
| 3529 | if $attrs->{$_} or $attrs->{"+$_"}; | ||||
| 3530 | } | ||||
| 3531 | |||||
| 3532 | # disassemble columns | ||||
| 3533 | 3000 | 839µs | my (@sel, @as); | ||
| 3534 | 3000 | 4.43ms | if (my $cols = delete $attrs->{columns}) { | ||
| 3535 | 3000 | 4.52ms | for my $c (ref $cols eq 'ARRAY' ? @$cols : $cols) { | ||
| 3536 | 60000 | 21.1ms | if (ref $c eq 'HASH') { | ||
| 3537 | for my $as (sort keys %$c) { | ||||
| 3538 | push @sel, $c->{$as}; | ||||
| 3539 | push @as, $as; | ||||
| 3540 | } | ||||
| 3541 | } | ||||
| 3542 | else { | ||||
| 3543 | 60000 | 14.8ms | push @sel, $c; | ||
| 3544 | 60000 | 15.0ms | push @as, $c; | ||
| 3545 | } | ||||
| 3546 | } | ||||
| 3547 | } | ||||
| 3548 | |||||
| 3549 | # when trying to weed off duplicates later do not go past this point - | ||||
| 3550 | # everything added from here on is unbalanced "anyone's guess" stuff | ||||
| 3551 | 3000 | 4.78ms | my $dedup_stop_idx = $#as; | ||
| 3552 | |||||
| 3553 | 3000 | 1.53ms | push @as, @{ ref $attrs->{as} eq 'ARRAY' ? $attrs->{as} : [ $attrs->{as} ] } | ||
| 3554 | if $attrs->{as}; | ||||
| 3555 | 3000 | 1.61ms | push @sel, @{ ref $attrs->{select} eq 'ARRAY' ? $attrs->{select} : [ $attrs->{select} ] } | ||
| 3556 | if $attrs->{select}; | ||||
| 3557 | |||||
| 3558 | # assume all unqualified selectors to apply to the current alias (legacy stuff) | ||||
| 3559 | 3000 | 246ms | 60000 | 13.1ms | $_ = (ref $_ or $_ =~ /\./) ? $_ : "$alias.$_" for @sel; # spent 13.1ms making 60000 calls to DBIx::Class::ResultSet::CORE:match, avg 218ns/call |
| 3560 | |||||
| 3561 | # disqualify all $alias.col as-bits (inflate-map mandated) | ||||
| 3562 | 3000 | 408ms | 120000 | 45.4ms | $_ = ($_ =~ /^\Q$alias.\E(.+)$/) ? $1 : $_ for @as; # spent 33.3ms making 60000 calls to DBIx::Class::ResultSet::CORE:regcomp, avg 554ns/call
# spent 12.1ms making 60000 calls to DBIx::Class::ResultSet::CORE:match, avg 202ns/call |
| 3563 | |||||
| 3564 | # de-duplicate the result (remove *identical* select/as pairs) | ||||
| 3565 | # and also die on duplicate {as} pointing to different {select}s | ||||
| 3566 | # not using a c-style for as the condition is prone to shrinkage | ||||
| 3567 | 3000 | 645µs | my $seen; | ||
| 3568 | 3000 | 1.24ms | my $i = 0; | ||
| 3569 | 3000 | 2.62ms | while ($i <= $dedup_stop_idx) { | ||
| 3570 | 60000 | 110ms | if ($seen->{"$sel[$i] \x00\x00 $as[$i]"}++) { | ||
| 3571 | splice @sel, $i, 1; | ||||
| 3572 | splice @as, $i, 1; | ||||
| 3573 | $dedup_stop_idx--; | ||||
| 3574 | } | ||||
| 3575 | elsif ($seen->{$as[$i]}++) { | ||||
| 3576 | $self->throw_exception( | ||||
| 3577 | "inflate_result() alias '$as[$i]' specified twice with different SQL-side {select}-ors" | ||||
| 3578 | ); | ||||
| 3579 | } | ||||
| 3580 | else { | ||||
| 3581 | 60000 | 6.23ms | $i++; | ||
| 3582 | } | ||||
| 3583 | } | ||||
| 3584 | |||||
| 3585 | 3000 | 5.28ms | $attrs->{select} = \@sel; | ||
| 3586 | 3000 | 3.36ms | $attrs->{as} = \@as; | ||
| 3587 | |||||
| 3588 | 3000 | 24.0ms | 3000 | 9.86ms | $attrs->{from} ||= [{ # spent 9.86ms making 3000 calls to DBIx::Class::ResultSource::Table::from, avg 3µs/call |
| 3589 | -rsrc => $source, | ||||
| 3590 | -alias => $self->{attrs}{alias}, | ||||
| 3591 | $self->{attrs}{alias} => $source->from, | ||||
| 3592 | }]; | ||||
| 3593 | |||||
| 3594 | 3000 | 3.21ms | if ( $attrs->{join} || $attrs->{prefetch} ) { | ||
| 3595 | |||||
| 3596 | $self->throw_exception ('join/prefetch can not be used with a custom {from}') | ||||
| 3597 | if ref $attrs->{from} ne 'ARRAY'; | ||||
| 3598 | |||||
| 3599 | my $join = (delete $attrs->{join}) || {}; | ||||
| 3600 | |||||
| 3601 | if ( defined $attrs->{prefetch} ) { | ||||
| 3602 | $join = $self->_merge_joinpref_attr( $join, $attrs->{prefetch} ); | ||||
| 3603 | } | ||||
| 3604 | |||||
| 3605 | $attrs->{from} = # have to copy here to avoid corrupting the original | ||||
| 3606 | [ | ||||
| 3607 | @{ $attrs->{from} }, | ||||
| 3608 | $source->_resolve_join( | ||||
| 3609 | $join, | ||||
| 3610 | $alias, | ||||
| 3611 | { %{ $attrs->{seen_join} || {} } }, | ||||
| 3612 | ( $attrs->{seen_join} && keys %{$attrs->{seen_join}}) | ||||
| 3613 | ? $attrs->{from}[-1][0]{-join_path} | ||||
| 3614 | : [] | ||||
| 3615 | , | ||||
| 3616 | ) | ||||
| 3617 | ]; | ||||
| 3618 | } | ||||
| 3619 | |||||
| 3620 | 3000 | 3.29ms | for my $attr (qw(order_by group_by)) { | ||
| 3621 | |||||
| 3622 | 6000 | 5.23ms | if ( defined $attrs->{$attr} ) { | ||
| 3623 | $attrs->{$attr} = ( | ||||
| 3624 | ref( $attrs->{$attr} ) eq 'ARRAY' | ||||
| 3625 | ? [ @{ $attrs->{$attr} } ] | ||||
| 3626 | : [ $attrs->{$attr} || () ] | ||||
| 3627 | ); | ||||
| 3628 | |||||
| 3629 | delete $attrs->{$attr} unless @{$attrs->{$attr}}; | ||||
| 3630 | } | ||||
| 3631 | } | ||||
| 3632 | |||||
| 3633 | # generate selections based on the prefetch helper | ||||
| 3634 | 3000 | 979µs | my ($prefetch, @prefetch_select, @prefetch_as); | ||
| 3635 | 3000 | 2.38ms | $prefetch = $self->_merge_joinpref_attr( {}, delete $attrs->{prefetch} ) | ||
| 3636 | if defined $attrs->{prefetch}; | ||||
| 3637 | |||||
| 3638 | 3000 | 1.00ms | if ($prefetch) { | ||
| 3639 | |||||
| 3640 | $self->throw_exception("Unable to prefetch, resultset contains an unnamed selector $attrs->{_dark_selector}{string}") | ||||
| 3641 | if $attrs->{_dark_selector}; | ||||
| 3642 | |||||
| 3643 | $self->throw_exception("Specifying prefetch in conjunction with an explicit collapse => 0 is unsupported") | ||||
| 3644 | if defined $attrs->{collapse} and ! $attrs->{collapse}; | ||||
| 3645 | |||||
| 3646 | $attrs->{collapse} = 1; | ||||
| 3647 | |||||
| 3648 | # this is a separate structure (we don't look in {from} directly) | ||||
| 3649 | # as the resolver needs to shift things off the lists to work | ||||
| 3650 | # properly (identical-prefetches on different branches) | ||||
| 3651 | my $join_map = {}; | ||||
| 3652 | if (ref $attrs->{from} eq 'ARRAY') { | ||||
| 3653 | |||||
| 3654 | my $start_depth = $attrs->{seen_join}{-relation_chain_depth} || 0; | ||||
| 3655 | |||||
| 3656 | for my $j ( @{$attrs->{from}}[1 .. $#{$attrs->{from}} ] ) { | ||||
| 3657 | next unless $j->[0]{-alias}; | ||||
| 3658 | next unless $j->[0]{-join_path}; | ||||
| 3659 | next if ($j->[0]{-relation_chain_depth} || 0) < $start_depth; | ||||
| 3660 | |||||
| 3661 | my @jpath = map { keys %$_ } @{$j->[0]{-join_path}}; | ||||
| 3662 | |||||
| 3663 | my $p = $join_map; | ||||
| 3664 | $p = $p->{$_} ||= {} for @jpath[ ($start_depth/2) .. $#jpath]; #only even depths are actual jpath boundaries | ||||
| 3665 | push @{$p->{-join_aliases} }, $j->[0]{-alias}; | ||||
| 3666 | } | ||||
| 3667 | } | ||||
| 3668 | |||||
| 3669 | my @prefetch = $source->_resolve_prefetch( $prefetch, $alias, $join_map ); | ||||
| 3670 | |||||
| 3671 | # save these for after distinct resolution | ||||
| 3672 | @prefetch_select = map { $_->[0] } @prefetch; | ||||
| 3673 | @prefetch_as = map { $_->[1] } @prefetch; | ||||
| 3674 | } | ||||
| 3675 | |||||
| 3676 | # run through the resulting joinstructure (starting from our current slot) | ||||
| 3677 | # and unset collapse if proven unnecessary | ||||
| 3678 | # | ||||
| 3679 | # also while we are at it find out if the current root source has | ||||
| 3680 | # been premultiplied by previous related_source chaining | ||||
| 3681 | # | ||||
| 3682 | # this allows to predict whether a root object with all other relation | ||||
| 3683 | # data set to NULL is in fact unique | ||||
| 3684 | 3000 | 1.63ms | if ($attrs->{collapse}) { | ||
| 3685 | |||||
| 3686 | if (ref $attrs->{from} eq 'ARRAY') { | ||||
| 3687 | |||||
| 3688 | if (@{$attrs->{from}} == 1) { | ||||
| 3689 | # no joins - no collapse | ||||
| 3690 | $attrs->{collapse} = 0; | ||||
| 3691 | } | ||||
| 3692 | else { | ||||
| 3693 | # find where our table-spec starts | ||||
| 3694 | my @fromlist = @{$attrs->{from}}; | ||||
| 3695 | while (@fromlist) { | ||||
| 3696 | my $t = shift @fromlist; | ||||
| 3697 | |||||
| 3698 | my $is_multi; | ||||
| 3699 | # me vs join from-spec distinction - a ref means non-root | ||||
| 3700 | if (ref $t eq 'ARRAY') { | ||||
| 3701 | $t = $t->[0]; | ||||
| 3702 | $is_multi ||= ! $t->{-is_single}; | ||||
| 3703 | } | ||||
| 3704 | last if ($t->{-alias} && $t->{-alias} eq $alias); | ||||
| 3705 | $attrs->{_main_source_premultiplied} ||= $is_multi; | ||||
| 3706 | } | ||||
| 3707 | |||||
| 3708 | # no non-singles remaining, nor any premultiplication - nothing to collapse | ||||
| 3709 | if ( | ||||
| 3710 | ! $attrs->{_main_source_premultiplied} | ||||
| 3711 | and | ||||
| 3712 | ! List::Util::first { ! $_->[0]{-is_single} } @fromlist | ||||
| 3713 | ) { | ||||
| 3714 | $attrs->{collapse} = 0; | ||||
| 3715 | } | ||||
| 3716 | } | ||||
| 3717 | } | ||||
| 3718 | |||||
| 3719 | else { | ||||
| 3720 | # if we can not analyze the from - err on the side of safety | ||||
| 3721 | $attrs->{_main_source_premultiplied} = 1; | ||||
| 3722 | } | ||||
| 3723 | } | ||||
| 3724 | |||||
| 3725 | # generate the distinct induced group_by before injecting the prefetched select/as parts | ||||
| 3726 | 3000 | 2.13ms | if (delete $attrs->{distinct}) { | ||
| 3727 | if ($attrs->{group_by}) { | ||||
| 3728 | carp_unique ("Useless use of distinct on a grouped resultset ('distinct' is ignored when a 'group_by' is present)"); | ||||
| 3729 | } | ||||
| 3730 | else { | ||||
| 3731 | $attrs->{_grouped_by_distinct} = 1; | ||||
| 3732 | # distinct affects only the main selection part, not what prefetch may add below | ||||
| 3733 | ($attrs->{group_by}, my $new_order) = $source->storage->_group_over_selection($attrs); | ||||
| 3734 | |||||
| 3735 | # FIXME possibly ignore a rewritten order_by (may turn out to be an issue) | ||||
| 3736 | # The thinking is: if we are collapsing the subquerying prefetch engine will | ||||
| 3737 | # rip stuff apart for us anyway, and we do not want to have a potentially | ||||
| 3738 | # function-converted external order_by | ||||
| 3739 | # ( there is an explicit if ( collapse && _grouped_by_distinct ) check in DBIHacks ) | ||||
| 3740 | $attrs->{order_by} = $new_order unless $attrs->{collapse}; | ||||
| 3741 | } | ||||
| 3742 | } | ||||
| 3743 | |||||
| 3744 | # inject prefetch-bound selection (if any) | ||||
| 3745 | 3000 | 2.69ms | push @{$attrs->{select}}, @prefetch_select; | ||
| 3746 | 3000 | 1.44ms | push @{$attrs->{as}}, @prefetch_as; | ||
| 3747 | |||||
| 3748 | 60000 | 174ms | 60000 | 9.91ms | $attrs->{_simple_passthrough_construction} = !( # spent 9.91ms making 60000 calls to DBIx::Class::ResultSet::CORE:match, avg 165ns/call |
| 3749 | $attrs->{collapse} | ||||
| 3750 | or | ||||
| 3751 | 3000 | 18.5ms | grep { $_ =~ /\./ } @{$attrs->{as}} | ||
| 3752 | ); | ||||
| 3753 | |||||
| 3754 | # if both page and offset are specified, produce a combined offset | ||||
| 3755 | # even though it doesn't make much sense, this is what pre 081xx has | ||||
| 3756 | # been doing | ||||
| 3757 | 3000 | 1.79ms | if (my $page = delete $attrs->{page}) { | ||
| 3758 | $attrs->{offset} = | ||||
| 3759 | ($attrs->{rows} * ($page - 1)) | ||||
| 3760 | + | ||||
| 3761 | ($attrs->{offset} || 0) | ||||
| 3762 | ; | ||||
| 3763 | } | ||||
| 3764 | |||||
| 3765 | 3000 | 27.6ms | return $self->{_attrs} = $attrs; | ||
| 3766 | } | ||||
| 3767 | |||||
| 3768 | sub _rollout_attr { | ||||
| 3769 | my ($self, $attr) = @_; | ||||
| 3770 | |||||
| 3771 | if (ref $attr eq 'HASH') { | ||||
| 3772 | return $self->_rollout_hash($attr); | ||||
| 3773 | } elsif (ref $attr eq 'ARRAY') { | ||||
| 3774 | return $self->_rollout_array($attr); | ||||
| 3775 | } else { | ||||
| 3776 | return [$attr]; | ||||
| 3777 | } | ||||
| 3778 | } | ||||
| 3779 | |||||
| 3780 | sub _rollout_array { | ||||
| 3781 | my ($self, $attr) = @_; | ||||
| 3782 | |||||
| 3783 | my @rolled_array; | ||||
| 3784 | foreach my $element (@{$attr}) { | ||||
| 3785 | if (ref $element eq 'HASH') { | ||||
| 3786 | push( @rolled_array, @{ $self->_rollout_hash( $element ) } ); | ||||
| 3787 | } elsif (ref $element eq 'ARRAY') { | ||||
| 3788 | # XXX - should probably recurse here | ||||
| 3789 | push( @rolled_array, @{$self->_rollout_array($element)} ); | ||||
| 3790 | } else { | ||||
| 3791 | push( @rolled_array, $element ); | ||||
| 3792 | } | ||||
| 3793 | } | ||||
| 3794 | return \@rolled_array; | ||||
| 3795 | } | ||||
| 3796 | |||||
| 3797 | sub _rollout_hash { | ||||
| 3798 | my ($self, $attr) = @_; | ||||
| 3799 | |||||
| 3800 | my @rolled_array; | ||||
| 3801 | foreach my $key (keys %{$attr}) { | ||||
| 3802 | push( @rolled_array, { $key => $attr->{$key} } ); | ||||
| 3803 | } | ||||
| 3804 | return \@rolled_array; | ||||
| 3805 | } | ||||
| 3806 | |||||
| 3807 | sub _calculate_score { | ||||
| 3808 | my ($self, $a, $b) = @_; | ||||
| 3809 | |||||
| 3810 | if (defined $a xor defined $b) { | ||||
| 3811 | return 0; | ||||
| 3812 | } | ||||
| 3813 | elsif (not defined $a) { | ||||
| 3814 | return 1; | ||||
| 3815 | } | ||||
| 3816 | |||||
| 3817 | if (ref $b eq 'HASH') { | ||||
| 3818 | my ($b_key) = keys %{$b}; | ||||
| 3819 | $b_key = '' if ! defined $b_key; | ||||
| 3820 | if (ref $a eq 'HASH') { | ||||
| 3821 | my ($a_key) = keys %{$a}; | ||||
| 3822 | $a_key = '' if ! defined $a_key; | ||||
| 3823 | if ($a_key eq $b_key) { | ||||
| 3824 | return (1 + $self->_calculate_score( $a->{$a_key}, $b->{$b_key} )); | ||||
| 3825 | } else { | ||||
| 3826 | return 0; | ||||
| 3827 | } | ||||
| 3828 | } else { | ||||
| 3829 | return ($a eq $b_key) ? 1 : 0; | ||||
| 3830 | } | ||||
| 3831 | } else { | ||||
| 3832 | if (ref $a eq 'HASH') { | ||||
| 3833 | my ($a_key) = keys %{$a}; | ||||
| 3834 | return ($b eq $a_key) ? 1 : 0; | ||||
| 3835 | } else { | ||||
| 3836 | return ($b eq $a) ? 1 : 0; | ||||
| 3837 | } | ||||
| 3838 | } | ||||
| 3839 | } | ||||
| 3840 | |||||
| 3841 | sub _merge_joinpref_attr { | ||||
| 3842 | my ($self, $orig, $import) = @_; | ||||
| 3843 | |||||
| 3844 | return $import unless defined($orig); | ||||
| 3845 | return $orig unless defined($import); | ||||
| 3846 | |||||
| 3847 | $orig = $self->_rollout_attr($orig); | ||||
| 3848 | $import = $self->_rollout_attr($import); | ||||
| 3849 | |||||
| 3850 | my $seen_keys; | ||||
| 3851 | foreach my $import_element ( @{$import} ) { | ||||
| 3852 | # find best candidate from $orig to merge $b_element into | ||||
| 3853 | my $best_candidate = { position => undef, score => 0 }; my $position = 0; | ||||
| 3854 | foreach my $orig_element ( @{$orig} ) { | ||||
| 3855 | my $score = $self->_calculate_score( $orig_element, $import_element ); | ||||
| 3856 | if ($score > $best_candidate->{score}) { | ||||
| 3857 | $best_candidate->{position} = $position; | ||||
| 3858 | $best_candidate->{score} = $score; | ||||
| 3859 | } | ||||
| 3860 | $position++; | ||||
| 3861 | } | ||||
| 3862 | my ($import_key) = ( ref $import_element eq 'HASH' ) ? keys %{$import_element} : ($import_element); | ||||
| 3863 | $import_key = '' if not defined $import_key; | ||||
| 3864 | |||||
| 3865 | if ($best_candidate->{score} == 0 || exists $seen_keys->{$import_key}) { | ||||
| 3866 | push( @{$orig}, $import_element ); | ||||
| 3867 | } else { | ||||
| 3868 | my $orig_best = $orig->[$best_candidate->{position}]; | ||||
| 3869 | # merge orig_best and b_element together and replace original with merged | ||||
| 3870 | if (ref $orig_best ne 'HASH') { | ||||
| 3871 | $orig->[$best_candidate->{position}] = $import_element; | ||||
| 3872 | } elsif (ref $import_element eq 'HASH') { | ||||
| 3873 | my ($key) = keys %{$orig_best}; | ||||
| 3874 | $orig->[$best_candidate->{position}] = { $key => $self->_merge_joinpref_attr($orig_best->{$key}, $import_element->{$key}) }; | ||||
| 3875 | } | ||||
| 3876 | } | ||||
| 3877 | $seen_keys->{$import_key} = 1; # don't merge the same key twice | ||||
| 3878 | } | ||||
| 3879 | |||||
| 3880 | return @$orig ? $orig : (); | ||||
| 3881 | } | ||||
| 3882 | |||||
| 3883 | { | ||||
| 3884 | 2 | 500ns | my $hm; | ||
| 3885 | |||||
| 3886 | # spent 102ms (18.4+83.5) within DBIx::Class::ResultSet::_merge_attr which was called 3000 times, avg 34µs/call:
# 3000 times (18.4ms+83.5ms) by DBIx::Class::ResultSet::_resolved_attrs at line 3528, avg 34µs/call | ||||
| 3887 | 3000 | 1.55ms | $hm ||= do { | ||
| 3888 | 1 | 235µs | require Hash::Merge; | ||
| 3889 | 1 | 5µs | 1 | 7µs | my $hm = Hash::Merge->new; # spent 7µs making 1 call to Hash::Merge::new |
| 3890 | |||||
| 3891 | $hm->specify_behavior({ | ||||
| 3892 | SCALAR => { | ||||
| 3893 | SCALAR => sub { | ||||
| 3894 | my ($defl, $defr) = map { defined $_ } (@_[0,1]); | ||||
| 3895 | |||||
| 3896 | if ($defl xor $defr) { | ||||
| 3897 | return [ $defl ? $_[0] : $_[1] ]; | ||||
| 3898 | } | ||||
| 3899 | elsif (! $defl) { | ||||
| 3900 | return []; | ||||
| 3901 | } | ||||
| 3902 | elsif (__HM_DEDUP and $_[0] eq $_[1]) { | ||||
| 3903 | return [ $_[0] ]; | ||||
| 3904 | } | ||||
| 3905 | else { | ||||
| 3906 | return [$_[0], $_[1]]; | ||||
| 3907 | } | ||||
| 3908 | }, | ||||
| 3909 | ARRAY => sub { | ||||
| 3910 | return $_[1] if !defined $_[0]; | ||||
| 3911 | return $_[1] if __HM_DEDUP and List::Util::first { $_ eq $_[0] } @{$_[1]}; | ||||
| 3912 | return [$_[0], @{$_[1]}] | ||||
| 3913 | }, | ||||
| 3914 | HASH => sub { | ||||
| 3915 | return [] if !defined $_[0] and !keys %{$_[1]}; | ||||
| 3916 | return [ $_[1] ] if !defined $_[0]; | ||||
| 3917 | return [ $_[0] ] if !keys %{$_[1]}; | ||||
| 3918 | return [$_[0], $_[1]] | ||||
| 3919 | }, | ||||
| 3920 | }, | ||||
| 3921 | ARRAY => { | ||||
| 3922 | # spent 7.89ms within DBIx::Class::ResultSet::__ANON__[/usr/share/perl5/DBIx/Class/ResultSet.pm:3926] which was called 3000 times, avg 3µs/call:
# 3000 times (7.89ms+0s) by Hash::Merge::merge at line 198 of Hash/Merge.pm, avg 3µs/call | ||||
| 3923 | 3000 | 25.8ms | return $_[0] if !defined $_[1]; | ||
| 3924 | return $_[0] if __HM_DEDUP and List::Util::first { $_ eq $_[1] } @{$_[0]}; | ||||
| 3925 | return [@{$_[0]}, $_[1]] | ||||
| 3926 | }, | ||||
| 3927 | ARRAY => sub { | ||||
| 3928 | my @ret = @{$_[0]} or return $_[1]; | ||||
| 3929 | return [ @ret, @{$_[1]} ] unless __HM_DEDUP; | ||||
| 3930 | my %idx = map { $_ => 1 } @ret; | ||||
| 3931 | push @ret, grep { ! defined $idx{$_} } (@{$_[1]}); | ||||
| 3932 | \@ret; | ||||
| 3933 | }, | ||||
| 3934 | HASH => sub { | ||||
| 3935 | return [ $_[1] ] if ! @{$_[0]}; | ||||
| 3936 | return $_[0] if !keys %{$_[1]}; | ||||
| 3937 | return $_[0] if __HM_DEDUP and List::Util::first { $_ eq $_[1] } @{$_[0]}; | ||||
| 3938 | return [ @{$_[0]}, $_[1] ]; | ||||
| 3939 | }, | ||||
| 3940 | }, | ||||
| 3941 | HASH => { | ||||
| 3942 | SCALAR => sub { | ||||
| 3943 | return [] if !keys %{$_[0]} and !defined $_[1]; | ||||
| 3944 | return [ $_[0] ] if !defined $_[1]; | ||||
| 3945 | return [ $_[1] ] if !keys %{$_[0]}; | ||||
| 3946 | return [$_[0], $_[1]] | ||||
| 3947 | }, | ||||
| 3948 | ARRAY => sub { | ||||
| 3949 | return [] if !keys %{$_[0]} and !@{$_[1]}; | ||||
| 3950 | return [ $_[0] ] if !@{$_[1]}; | ||||
| 3951 | return $_[1] if !keys %{$_[0]}; | ||||
| 3952 | return $_[1] if __HM_DEDUP and List::Util::first { $_ eq $_[0] } @{$_[1]}; | ||||
| 3953 | return [ $_[0], @{$_[1]} ]; | ||||
| 3954 | }, | ||||
| 3955 | HASH => sub { | ||||
| 3956 | return [] if !keys %{$_[0]} and !keys %{$_[1]}; | ||||
| 3957 | return [ $_[0] ] if !keys %{$_[1]}; | ||||
| 3958 | return [ $_[1] ] if !keys %{$_[0]}; | ||||
| 3959 | return [ $_[0] ] if $_[0] eq $_[1]; | ||||
| 3960 | return [ $_[0], $_[1] ]; | ||||
| 3961 | }, | ||||
| 3962 | } | ||||
| 3963 | 1 | 22µs | 1 | 19µs | } => 'DBIC_RS_ATTR_MERGER'); # spent 19µs making 1 call to Hash::Merge::specify_behavior |
| 3964 | 1 | 400ns | $hm; | ||
| 3965 | }; | ||||
| 3966 | |||||
| 3967 | 3000 | 20.1ms | 3000 | 83.3ms | return $hm->merge ($_[1], $_[2]); # spent 83.3ms making 3000 calls to Hash::Merge::merge, avg 28µs/call |
| 3968 | } | ||||
| 3969 | } | ||||
| 3970 | |||||
| 3971 | sub STORABLE_freeze { | ||||
| 3972 | my ($self, $cloning) = @_; | ||||
| 3973 | my $to_serialize = { %$self }; | ||||
| 3974 | |||||
| 3975 | # A cursor in progress can't be serialized (and would make little sense anyway) | ||||
| 3976 | # the parser can be regenerated (and can't be serialized) | ||||
| 3977 | delete @{$to_serialize}{qw/cursor _row_parser _result_inflator/}; | ||||
| 3978 | |||||
| 3979 | # nor is it sensical to store a not-yet-fired-count pager | ||||
| 3980 | if ($to_serialize->{pager} and ref $to_serialize->{pager}{total_entries} eq 'CODE') { | ||||
| 3981 | delete $to_serialize->{pager}; | ||||
| 3982 | } | ||||
| 3983 | |||||
| 3984 | Storable::nfreeze($to_serialize); | ||||
| 3985 | } | ||||
| 3986 | |||||
| 3987 | # need this hook for symmetry | ||||
| 3988 | sub STORABLE_thaw { | ||||
| 3989 | my ($self, $cloning, $serialized) = @_; | ||||
| 3990 | |||||
| 3991 | %$self = %{ Storable::thaw($serialized) }; | ||||
| 3992 | |||||
| 3993 | $self; | ||||
| 3994 | } | ||||
| 3995 | |||||
| 3996 | |||||
| 3997 | =head2 throw_exception | ||||
| 3998 | |||||
| 3999 | See L<DBIx::Class::Schema/throw_exception> for details. | ||||
| 4000 | |||||
| 4001 | =cut | ||||
| 4002 | |||||
| 4003 | sub throw_exception { | ||||
| 4004 | my $self=shift; | ||||
| 4005 | |||||
| 4006 | if (ref $self and my $rsrc = $self->result_source) { | ||||
| 4007 | $rsrc->throw_exception(@_) | ||||
| 4008 | } | ||||
| 4009 | else { | ||||
| 4010 | DBIx::Class::Exception->throw(@_); | ||||
| 4011 | } | ||||
| 4012 | } | ||||
| 4013 | |||||
| 4014 | 1 | 5µs | 1; | ||
| 4015 | |||||
| 4016 | 1 | 379µs | __END__ # spent 379µs making 1 call to B::Hooks::EndOfScope::XS::__ANON__ | ||
# spent 42.6ms within DBIx::Class::ResultSet::CORE:match which was called 186000 times, avg 229ns/call:
# 60000 times (13.1ms+0s) by DBIx::Class::ResultSet::_resolved_attrs at line 3559, avg 218ns/call
# 60000 times (12.1ms+0s) by DBIx::Class::ResultSet::_resolved_attrs at line 3562, avg 202ns/call
# 60000 times (9.91ms+0s) by DBIx::Class::ResultSet::_resolved_attrs at line 3748, avg 165ns/call
# 3000 times (4.88ms+0s) by DBIx::Class::ResultSet::_remove_alias at line 2721, avg 2µs/call
# 3000 times (2.55ms+0s) by DBIx::Class::ResultSet::_qualify_cond_columns at line 943, avg 850ns/call | |||||
# spent 33.3ms within DBIx::Class::ResultSet::CORE:regcomp which was called 60000 times, avg 554ns/call:
# 60000 times (33.3ms+0s) by DBIx::Class::ResultSet::_resolved_attrs at line 3562, avg 554ns/call |