Merge 'trunk' into 'prefetch'
Peter Rabbitson [Mon, 21 Sep 2009 11:34:26 +0000 (11:34 +0000)]
r7673@Thesaurus (orig r7662):  ribasushi | 2009-09-15 09:43:46 +0200
Warn when distinct is used with group_by
r7674@Thesaurus (orig r7663):  rbuels | 2009-09-15 22:45:32 +0200
doc patch, clarified warning about using find_or_create() and friends on tables with auto-increment or similar columns
r7675@Thesaurus (orig r7664):  rbuels | 2009-09-15 22:55:15 +0200
another doc clarification regarding auto-inc columns with find_or_create() and such functions
r7683@Thesaurus (orig r7672):  ribasushi | 2009-09-17 13:54:44 +0200
Fix left-join chaining
r7694@Thesaurus (orig r7683):  ribasushi | 2009-09-18 12:36:42 +0200
 r6389@Thesaurus (orig r6388):  caelum | 2009-05-23 22:48:06 +0200
 recreating Sybase branch
 r6395@Thesaurus (orig r6394):  caelum | 2009-05-24 01:47:32 +0200
 try not to fuck mssql with the sybase crap
 r6488@Thesaurus (orig r6487):  caelum | 2009-06-03 17:31:24 +0200
 resolve conflict
 r6490@Thesaurus (orig r6489):  caelum | 2009-06-03 18:25:36 +0200
 add missing files to sybase branch
 r6492@Thesaurus (orig r6491):  caelum | 2009-06-04 01:51:39 +0200
 fix Sybase DT stuff and storage bases
 r6493@Thesaurus (orig r6492):  caelum | 2009-06-04 02:10:45 +0200
 fix base for mssql (can't be a sybase anymore)
 r6494@Thesaurus (orig r6493):  caelum | 2009-06-04 02:20:37 +0200
 test sybase SMALLDATETIME inflation
 r6495@Thesaurus (orig r6494):  caelum | 2009-06-04 04:52:31 +0200
 update Sybase docs
 r6501@Thesaurus (orig r6500):  caelum | 2009-06-04 14:50:49 +0200
 sybase limit count without offset now works
 r6504@Thesaurus (orig r6503):  caelum | 2009-06-04 18:03:01 +0200
 use TOP for sybase limit count thanks to refactored count
 r6505@Thesaurus (orig r6504):  caelum | 2009-06-04 18:41:54 +0200
 back to counting rows for Sybase LIMIT counts
 r6506@Thesaurus (orig r6505):  caelum | 2009-06-04 19:07:48 +0200
 minor sybase count fix
 r6512@Thesaurus (orig r6511):  caelum | 2009-06-05 01:02:48 +0200
 test sybase group_by count, works
 r6513@Thesaurus (orig r6512):  caelum | 2009-06-05 01:28:18 +0200
 set date format on _rebless correctly
 r6516@Thesaurus (orig r6515):  caelum | 2009-06-05 02:24:46 +0200
 manually merged in sybase_noquote branch
 r6518@Thesaurus (orig r6517):  caelum | 2009-06-05 06:34:25 +0200
 shit doesn't work yet
 r6520@Thesaurus (orig r6519):  caelum | 2009-06-05 16:55:41 +0200
 update sybase types which shouldn't be quoted
 r6525@Thesaurus (orig r6524):  caelum | 2009-06-06 04:40:51 +0200
 tweaks to sybase types
 r6527@Thesaurus (orig r6526):  caelum | 2009-06-06 05:36:03 +0200
 temporary sybase noquote hack
 r6595@Thesaurus (orig r6594):  caelum | 2009-06-10 13:46:37 +0200
 Sybase::NoBindVars now correctly quotes
 r6596@Thesaurus (orig r6595):  caelum | 2009-06-10 14:04:19 +0200
 cache rsrc in NoBindVars, use name_sep
 r6597@Thesaurus (orig r6596):  caelum | 2009-06-10 14:35:52 +0200
 Sybase count by first pk, if available
 r6599@Thesaurus (orig r6598):  caelum | 2009-06-10 15:00:42 +0200
 cache rsrc in NoBindVars correctly
 r6600@Thesaurus (orig r6599):  caelum | 2009-06-10 15:27:41 +0200
 handle unknown rsrc in NoBindVars and Sybase::NoBindVars
 r6605@Thesaurus (orig r6604):  caelum | 2009-06-10 18:17:31 +0200
 cache rsrc properly in NoBindVars, return undef if no rsrc
 r6658@Thesaurus (orig r6657):  caelum | 2009-06-13 05:57:40 +0200
 switch to DateTime::Format::Sybase
 r6700@Thesaurus (orig r6699):  caelum | 2009-06-17 16:25:28 +0200
 rename and document dt setup method, will be an on_connect_call at later merge point
 r6701@Thesaurus (orig r6700):  caelum | 2009-06-17 16:30:08 +0200
 more dt docs reorg
 r6715@Thesaurus (orig r6714):  caelum | 2009-06-19 01:28:17 +0200
 todo tests for text/image columns in sybase
 r6716@Thesaurus (orig r6715):  caelum | 2009-06-19 01:46:56 +0200
 added connect_call_blob_setup for Sybase
 r6724@Thesaurus (orig r6723):  caelum | 2009-06-19 17:12:20 +0200
 cleanups
 r6771@Thesaurus (orig r6770):  caelum | 2009-06-23 16:42:32 +0200
 minor changes
 r6788@Thesaurus (orig r6787):  caelum | 2009-06-25 05:31:06 +0200
 fixup POD, comment out count
 r6811@Thesaurus (orig r6810):  caelum | 2009-06-28 02:14:56 +0200
 prototype blob implementation
 r6857@Thesaurus (orig r6856):  caelum | 2009-06-29 23:45:19 +0200
 branch pushed, removing
 r6868@Thesaurus (orig r6867):  caelum | 2009-06-30 03:39:51 +0200
 merge on_connect_call updates
 r6877@Thesaurus (orig r6876):  caelum | 2009-06-30 12:46:43 +0200
 code cleanups
 r6957@Thesaurus (orig r6956):  caelum | 2009-07-03 02:32:48 +0200
 minor changes
 r6959@Thesaurus (orig r6958):  caelum | 2009-07-03 05:04:12 +0200
 fix sybase mro
 r7001@Thesaurus (orig r7000):  caelum | 2009-07-07 13:34:23 +0200
 fix sybase rebless to NoBindVars
 r7021@Thesaurus (orig r7020):  caelum | 2009-07-10 12:52:13 +0200
 fix NoBindVars
 r7053@Thesaurus (orig r7052):  caelum | 2009-07-15 01:39:02 +0200
 set maxConnect in DSN and add docs
 r7065@Thesaurus (orig r7064):  caelum | 2009-07-17 09:39:54 +0200
 make insertion of blobs into tables with identity columns work, other minor fixes
 r7070@Thesaurus (orig r7069):  caelum | 2009-07-17 23:30:13 +0200
 some compatibility updated for older DBD::Sybase versions, some initial work on _select_args for blobs
 r7072@Thesaurus (orig r7071):  caelum | 2009-07-19 23:57:11 +0200
 mangling _select_args turned out to be unnecessary
 r7073@Thesaurus (orig r7072):  caelum | 2009-07-20 01:02:19 +0200
 minor cleanups
 r7074@Thesaurus (orig r7073):  caelum | 2009-07-20 15:47:48 +0200
 blob update now works
 r7076@Thesaurus (orig r7075):  caelum | 2009-07-20 19:06:46 +0200
 change the (incorrect) version check to a check for FreeTDS
 r7077@Thesaurus (orig r7076):  caelum | 2009-07-20 19:13:25 +0200
 better check for FreeTDS thanks to arcanez
 r7089@Thesaurus (orig r7086):  caelum | 2009-07-22 07:09:21 +0200
 minor cleanups
 r7091@Thesaurus (orig r7088):  caelum | 2009-07-22 17:05:37 +0200
 remove unnecessary test Result class
 r7092@Thesaurus (orig r7089):  caelum | 2009-07-23 00:47:14 +0200
 fix doc for how to check for FreeTDS
 r7095@Thesaurus (orig r7092):  caelum | 2009-07-23 14:35:53 +0200
 doc tweak
 r7115@Thesaurus (orig r7112):  caelum | 2009-07-24 09:58:24 +0200
 add support for IDENTITY_INSERT
 r7117@Thesaurus (orig r7114):  caelum | 2009-07-24 16:19:08 +0200
 savepoint support
 r7120@Thesaurus (orig r7117):  caelum | 2009-07-24 20:35:37 +0200
 fix race condition in last_insert_id with placeholders
 r7121@Thesaurus (orig r7118):  caelum | 2009-07-24 21:22:25 +0200
 code cleanup
 r7124@Thesaurus (orig r7121):  caelum | 2009-07-25 16:19:58 +0200
 use _resolve_column_info in NoBindVars
 r7125@Thesaurus (orig r7122):  caelum | 2009-07-25 21:23:49 +0200
 make insert work as a nested transaction too
 r7126@Thesaurus (orig r7123):  caelum | 2009-07-25 22:52:17 +0200
 add money type support
 r7128@Thesaurus (orig r7125):  caelum | 2009-07-27 03:48:35 +0200
 better FreeTDS support
 r7130@Thesaurus (orig r7127):  caelum | 2009-07-28 06:23:54 +0200
 minor refactoring, cleanups, doc updates
 r7131@Thesaurus (orig r7128):  caelum | 2009-07-28 09:32:45 +0200
 forgot to set mro in dbi::cursor
 r7141@Thesaurus (orig r7138):  caelum | 2009-07-30 10:21:20 +0200
 better test for "smalldatetime" in Sybase
 r7146@Thesaurus (orig r7143):  caelum | 2009-07-30 15:37:18 +0200
 update sqlite test schema
 r7207@Thesaurus (orig r7204):  caelum | 2009-08-04 23:40:16 +0200
 update Changes
 r7222@Thesaurus (orig r7219):  caelum | 2009-08-05 11:02:26 +0200
 fix a couple minor issues after pull from trunk
 r7260@Thesaurus (orig r7257):  caelum | 2009-08-07 14:45:18 +0200
 add note about where to get Schema::Loader
 r7273@Thesaurus (orig r7270):  ribasushi | 2009-08-09 01:19:49 +0200
 Changes and minor code rewrap
 r7285@Thesaurus (orig r7282):  ribasushi | 2009-08-10 08:08:06 +0200
 pesky whitespace
 r7286@Thesaurus (orig r7283):  ribasushi | 2009-08-10 08:11:46 +0200
 privatize dormant method - it may be useful for sybase at *some* point
 r7287@Thesaurus (orig r7284):  ribasushi | 2009-08-10 08:19:55 +0200
 Whoops
 r7289@Thesaurus (orig r7286):  caelum | 2009-08-10 08:44:51 +0200
 document placeholders_with_type_conversion_supported and add a redispatch to reblessed storage in DBI::update
 r7290@Thesaurus (orig r7287):  caelum | 2009-08-10 10:07:45 +0200
 fix and test redispatch to reblessed storage insert/update
 r7292@Thesaurus (orig r7289):  caelum | 2009-08-10 10:32:37 +0200
 rename get_connected_schema to get_schema in sybase test
 r7345@Thesaurus (orig r7342):  ribasushi | 2009-08-18 22:45:06 +0200
 Fix Changes
 r7367@Thesaurus (orig r7364):  ribasushi | 2009-08-23 10:00:34 +0200
 Minaor speedup
 r7368@Thesaurus (orig r7365):  ribasushi | 2009-08-23 10:01:10 +0200
 Generalize and hide placeholder support check
 r7369@Thesaurus (orig r7366):  ribasushi | 2009-08-23 10:04:26 +0200
 Rename the common sybase driver
 r7373@Thesaurus (orig r7370):  caelum | 2009-08-24 13:21:51 +0200
 make insert only use a txn if needed, add connect_call_unsafe_insert
 r7374@Thesaurus (orig r7371):  caelum | 2009-08-24 14:42:57 +0200
 add test for IDENTITY_INSERT
 r7378@Thesaurus (orig r7375):  caelum | 2009-08-24 15:51:48 +0200
 use debugobj->callback instead of local *_query_start in test to capture query
 r7379@Thesaurus (orig r7376):  caelum | 2009-08-24 17:19:46 +0200
 remove duplicate oracle method and fix an mssql method call
 r7417@Thesaurus (orig r7414):  caelum | 2009-08-29 07:23:45 +0200
 update link to Schema::Loader branch
 r7427@Thesaurus (orig r7424):  caelum | 2009-08-29 09:31:41 +0200
 switch to ::DBI::AutoCast
 r7428@Thesaurus (orig r7425):  ribasushi | 2009-08-29 13:36:22 +0200
 Cleanup:
 Added commented method signatures for easier debugging
 privatize transform_unbound_value as _prep_bind_value
 Remove \@_ splice's in lieu of of simple shifts
 Exposed TYPE_MAPPING used by native_data_type via our
 Removed use of txn_do - internal code uses the scope guard
 Renamed some variables, whitespace cleanup, the works
 r7429@Thesaurus (orig r7426):  ribasushi | 2009-08-29 13:40:48 +0200
 Varname was absolutely correct
 r7430@Thesaurus (orig r7427):  caelum | 2009-08-29 14:09:13 +0200
 minor changes for tests to pass again
 r7431@Thesaurus (orig r7428):  caelum | 2009-08-29 21:08:51 +0200
 fix inserts with active cursors
 r7432@Thesaurus (orig r7429):  caelum | 2009-08-29 22:53:02 +0200
 remove extra connection
 r7434@Thesaurus (orig r7431):  caelum | 2009-08-30 00:02:20 +0200
 test correlated subquery
 r7442@Thesaurus (orig r7439):  ribasushi | 2009-08-30 09:07:00 +0200
 Put the ocmment back
 r7443@Thesaurus (orig r7440):  ribasushi | 2009-08-30 09:15:41 +0200
 Change should_quote_value to interpolate_unquoted to make it harder to stop quoting by accident (it's easier to return a undef by accident than a 1)
 r7446@Thesaurus (orig r7443):  caelum | 2009-08-30 18:19:46 +0200
 added txn_scope_guards for blob operations
 r7447@Thesaurus (orig r7444):  ribasushi | 2009-08-30 18:56:43 +0200
 Rename insert_txn to unsafe_insert
 r7512@Thesaurus (orig r7509):  ribasushi | 2009-09-03 20:24:14 +0200
 Minor cleanups
 r7575@Thesaurus (orig r7572):  caelum | 2009-09-05 07:23:57 +0200
 pending review by mpeppler
 r7593@Thesaurus (orig r7590):  ribasushi | 2009-09-07 09:10:05 +0200
 Release 0.08111 tag
 r7594@Thesaurus (orig r7591):  ribasushi | 2009-09-07 09:14:33 +0200
 Whoops this should not have committed
 r7602@Thesaurus (orig r7599):  caelum | 2009-09-07 21:31:38 +0200
 fix _insert_dbh code to only connect when needed, doc update
 r7607@Thesaurus (orig r7604):  caelum | 2009-09-09 02:15:54 +0200
 remove unsafe_insert
 r7608@Thesaurus (orig r7605):  ribasushi | 2009-09-09 09:14:20 +0200
 Localisation ain't free, we don't do it unless we have to
 r7609@Thesaurus (orig r7606):  ribasushi | 2009-09-09 09:40:29 +0200
 Much simpler
 r7610@Thesaurus (orig r7607):  ribasushi | 2009-09-09 10:38:41 +0200
 Reduce amount of perl-golf :)
 r7611@Thesaurus (orig r7608):  ribasushi | 2009-09-09 10:41:15 +0200
 This should not have worked - I guess we lack tests?
 r7614@Thesaurus (orig r7611):  caelum | 2009-09-09 12:08:36 +0200
 test multi-row blob update
 r7619@Thesaurus (orig r7616):  caelum | 2009-09-09 18:01:15 +0200
 remove Sub::Name hack for method dispatch, pass $next instead
 r7620@Thesaurus (orig r7617):  caelum | 2009-09-10 02:16:03 +0200
 do blob update over _insert_dbh
 r7661@Thesaurus (orig r7650):  caelum | 2009-09-13 10:27:44 +0200
 change _insert_dbh to _insert_storage
 r7663@Thesaurus (orig r7652):  caelum | 2009-09-13 11:52:20 +0200
 make sure _init doesn't loop, steal insert_bulk from mssql, add some insert_bulk tests
 r7664@Thesaurus (orig r7653):  caelum | 2009-09-13 13:27:51 +0200
 allow subclassing of methods proxied to _writer_storage
 r7666@Thesaurus (orig r7655):  caelum | 2009-09-14 15:09:21 +0200
 sybase bulk API support stuff (no blobs yet, coming soon...)
 r7667@Thesaurus (orig r7656):  caelum | 2009-09-14 15:33:14 +0200
 add another test for sybase bulk stuff (passes)
 r7668@Thesaurus (orig r7657):  caelum | 2009-09-14 15:44:06 +0200
 minor change (fix inverted boolean for warning)
 r7669@Thesaurus (orig r7658):  caelum | 2009-09-14 15:48:52 +0200
 remove @args from DBI::sth, use full arg list
 r7676@Thesaurus (orig r7665):  caelum | 2009-09-16 15:06:35 +0200
 use execute_array for insert_bulk, test insert_bulk with blobs, clean up blob tests a bit
 r7680@Thesaurus (orig r7669):  ribasushi | 2009-09-16 19:36:19 +0200
 Remove branched changes
 r7682@Thesaurus (orig r7671):  caelum | 2009-09-17 03:03:34 +0200
 I'll rewrite this bit tomorrow to be less retarded
 r7684@Thesaurus (orig r7673):  caelum | 2009-09-18 04:03:15 +0200
 fix yesterday's stuff, identity_update works, blob updates are better
 r7686@Thesaurus (orig r7675):  caelum | 2009-09-18 04:22:38 +0200
 column no longer necessary in test
 r7688@Thesaurus (orig r7677):  caelum | 2009-09-18 08:33:14 +0200
 fix freetds
 r7691@Thesaurus (orig r7680):  ribasushi | 2009-09-18 12:25:42 +0200
  r7678@Thesaurus (orig r7667):  ribasushi | 2009-09-16 19:31:14 +0200
  New subbranch
  r7679@Thesaurus (orig r7668):  ribasushi | 2009-09-16 19:34:29 +0200
  Caelum's work so far
  r7690@Thesaurus (orig r7679):  caelum | 2009-09-18 11:10:16 +0200
  support for blobs in insert_bulk fallback

 r7692@Thesaurus (orig r7681):  ribasushi | 2009-09-18 12:28:09 +0200
 Rollback all bulk insert code before merge

r7699@Thesaurus (orig r7688):  ribasushi | 2009-09-18 14:12:05 +0200
Cleanup exception handling
r7700@Thesaurus (orig r7689):  ribasushi | 2009-09-18 14:22:02 +0200
duh
r7701@Thesaurus (orig r7690):  ribasushi | 2009-09-18 14:25:06 +0200
Minor cleanup of RSC with has_many joins
r7702@Thesaurus (orig r7691):  ribasushi | 2009-09-18 14:32:15 +0200
Changes and dev notes in makefile
r7705@Thesaurus (orig r7694):  ribasushi | 2009-09-18 14:52:26 +0200
Nothing says the grouping column can not be nullable
r7706@Thesaurus (orig r7695):  ribasushi | 2009-09-18 14:53:33 +0200
Changes
r7707@Thesaurus (orig r7696):  ribasushi | 2009-09-18 20:09:04 +0200
This code belogs in Storage::DBI
r7708@Thesaurus (orig r7697):  ribasushi | 2009-09-18 20:38:26 +0200
Clear up some legacy cruft and straighten inheritance
r7710@Thesaurus (orig r7699):  ribasushi | 2009-09-21 00:25:20 +0200
Backout sybase changes
r7713@Thesaurus (orig r7702):  ribasushi | 2009-09-21 00:46:32 +0200
Missed a part of the revert
r7720@Thesaurus (orig r7709):  ribasushi | 2009-09-21 02:49:11 +0200
Oops
r7721@Thesaurus (orig r7710):  ribasushi | 2009-09-21 11:02:14 +0200
Changes
r7722@Thesaurus (orig r7711):  ribasushi | 2009-09-21 12:49:30 +0200
Undocument the from attribute (the description was mostly outdated anyway)
r7723@Thesaurus (orig r7712):  ribasushi | 2009-09-21 12:58:58 +0200
Release 0.08112

lib/DBIx/Class/ResultSet.pm
lib/DBIx/Class/ResultSource.pm
t/76select.t
t/inflate/hri.t
t/prefetch/diamond.t
t/prefetch/grouped.t
t/prefetch/multiple_hasmany.t
t/prefetch/with_limit.t

index 45e838f..ac349bb 100644 (file)
@@ -971,140 +971,183 @@ sub _construct_object {
   return @new;
 }
 
-sub _collapse_result {
-  my ($self, $as_proto, $row) = @_;
-
-  # if the first row that ever came in is totally empty - this means we got
-  # hit by a smooth^Wempty left-joined resultset. Just noop in that case
-  # instead of producing a {}
-  #
-  my $has_def;
-  for (@$row) {
-    if (defined $_) {
-      $has_def++;
-      last;
+# _unflatten_result takes a row hashref which looks like this:
+# $VAR1 = {
+#   'cd.artist.artistid' => '1',
+#   'cd.artist' => '1',
+#   'cd_id' => '1',
+#   'cd.genreid' => undef,
+#   'cd.year' => '1999',
+#   'cd.title' => 'Spoonful of bees',
+#   'cd.single_track' => undef,
+#   'cd.artist.name' => 'Caterwauler McCrae',
+#   'cd.artist.rank' => '13',
+#   'cd.artist.charfield' => undef,
+#   'cd.cdid' => '1'
+# };
+
+# and generates the following structure:
+
+# $VAR1 = [
+#   {
+#     'cd_id' => '1'
+#   },
+#   {
+#     'cd' => [
+#       {
+#         'single_track' => undef,
+#         'cdid' => '1',
+#         'artist' => '1',
+#         'title' => 'Spoonful of bees',
+#         'year' => '1999',
+#         'genreid' => undef
+#       },
+#       {
+#         'artist' => [
+#           {
+#             'artistid' => '1',
+#             'charfield' => undef,
+#             'name' => 'Caterwauler McCrae',
+#             'rank' => '13'
+#           }
+#         ]
+#       }
+#     ]
+#   }
+# ];  
+
+# It returns one row object which consists of an arrayref with two
+# elements. The first contains the plain column data, the second 
+# contains the data of relationships. Those are row arrayrefs, themselves.
+
+# it's a recursive function. It needs to request the relationship_info
+# to decide whether to put the data of a relationship in a hashref
+# (i.e. belongs_to) or an arrayref (i.e. has_many).
+
+sub _unflatten_result {
+    my ( $self, $row ) = @_;
+
+    my $columns = {};
+    my $rels    = {};
+
+    foreach my $column ( sort keys %$row ) {
+        if ( $column =~ /^(.*?)\.(.*)$/ ) {
+            $rels->{$1} ||= {};
+            $rels->{$1}->{$2} = $row->{$column};
+        }
+        else {
+            $columns->{$column} = $row->{$column};
+        }
     }
-  }
-  return undef unless $has_def;
-
-  my @copy = @$row;
-
-  # 'foo'         => [ undef, 'foo' ]
-  # 'foo.bar'     => [ 'foo', 'bar' ]
-  # 'foo.bar.baz' => [ 'foo.bar', 'baz' ]
-
-  my @construct_as = map { [ (/^(?:(.*)\.)?([^.]+)$/) ] } @$as_proto;
-
-  my %collapse = %{$self->{_attrs}{collapse}||{}};
-
-  my @pri_index;
 
-  # if we're doing collapsing (has_many prefetch) we need to grab records
-  # until the PK changes, so fill @pri_index. if not, we leave it empty so
-  # we know we don't have to bother.
-
-  # the reason for not using the collapse stuff directly is because if you
-  # had for e.g. two artists in a row with no cds, the collapse info for
-  # both would be NULL (undef) so you'd lose the second artist
-
-  # store just the index so we can check the array positions from the row
-  # without having to contruct the full hash
-
-  if (keys %collapse) {
-    my %pri = map { ($_ => 1) } $self->result_source->primary_columns;
-    foreach my $i (0 .. $#construct_as) {
-      next if defined($construct_as[$i][0]); # only self table
-      if (delete $pri{$construct_as[$i][1]}) {
-        push(@pri_index, $i);
-      }
-      last unless keys %pri; # short circuit (Johnny Five Is Alive!)
+    foreach my $rel ( sort keys %$rels ) {
+        my $rel_info = $self->result_source->relationship_info($rel);
+        $rels->{$rel} =
+          $self->related_resultset($rel)->_unflatten_result( $rels->{$rel} );
+        $rels->{$rel} = [ $rels->{$rel} ]
+          if ( $rel_info->{attrs}->{accessor} eq 'multi' );
     }
-  }
-
-  # no need to do an if, it'll be empty if @pri_index is empty anyway
-
-  my %pri_vals = map { ($_ => $copy[$_]) } @pri_index;
 
-  my @const_rows;
+    return keys %$rels ? [ $columns, $rels ] : [$columns];
+}
 
-  do { # no need to check anything at the front, we always want the first row
+# two arguments: $as_proto is an arrayref of column names,
+# $row_ref is an arrayref of the data. If none of the row data
+# is defined we return undef (that's copied from the old
+# _collapse_result). Next we decide whether we need to collapse
+# the resultset (i.e. we prefetch something) or not. $collapse
+# indicates that. The do-while loop will run once if we do not need
+# to collapse the result and will run as long as _merge_result returns
+# a true value. It will return undef if the current added row does not
+# match the previous row. A bit of stashing and cursor magic is
+# required so that the cursor is not mixed up.
 
-    my %const;
+# "$rows" is a bit misleading. In the end, there should only be one
+# element in this arrayref. 
 
-    foreach my $this_as (@construct_as) {
-      $const{$this_as->[0]||''}{$this_as->[1]} = shift(@copy);
+sub _collapse_result {
+    my ( $self, $as_proto, $row_ref ) = @_;
+    my $has_def;
+    for (@$row_ref) {
+        if ( defined $_ ) {
+            $has_def++;
+            last;
+        }
     }
-
-    push(@const_rows, \%const);
-
-  } until ( # no pri_index => no collapse => drop straight out
-      !@pri_index
-    or
-      do { # get another row, stash it, drop out if different PK
-
-        @copy = $self->cursor->next;
-        $self->{stashed_row} = \@copy;
-
-        # last thing in do block, counts as true if anything doesn't match
-
-        # check xor defined first for NULL vs. NOT NULL then if one is
-        # defined the other must be so check string equality
-
-        grep {
-          (defined $pri_vals{$_} ^ defined $copy[$_])
-          || (defined $pri_vals{$_} && ($pri_vals{$_} ne $copy[$_]))
-        } @pri_index;
-      }
-  );
-
-  my $alias = $self->{attrs}{alias};
-  my $info = [];
-
-  my %collapse_pos;
-
-  my @const_keys;
-
-  foreach my $const (@const_rows) {
-    scalar @const_keys or do {
-      @const_keys = sort { length($a) <=> length($b) } keys %$const;
-    };
-    foreach my $key (@const_keys) {
-      if (length $key) {
-        my $target = $info;
-        my @parts = split(/\./, $key);
-        my $cur = '';
-        my $data = $const->{$key};
-        foreach my $p (@parts) {
-          $target = $target->[1]->{$p} ||= [];
-          $cur .= ".${p}";
-          if ($cur eq ".${key}" && (my @ckey = @{$collapse{$cur}||[]})) {
-            # collapsing at this point and on final part
-            my $pos = $collapse_pos{$cur};
-            CK: foreach my $ck (@ckey) {
-              if (!defined $pos->{$ck} || $pos->{$ck} ne $data->{$ck}) {
-                $collapse_pos{$cur} = $data;
-                delete @collapse_pos{ # clear all positioning for sub-entries
-                  grep { m/^\Q${cur}.\E/ } keys %collapse_pos
-                };
-                push(@$target, []);
-                last CK;
-              }
+    return undef unless $has_def;
+
+    my $collapse = keys %{ $self->{_attrs}{collapse} || {} };
+    my $rows     = [];
+    my @row      = @$row_ref;
+    do {
+        my $i = 0;
+        my $row = { map { $_ => $row[ $i++ ] } @$as_proto };
+        $row = $self->_unflatten_result($row);
+        unless ( scalar @$rows ) {
+            push( @$rows, $row );
+        }
+        $collapse = undef unless ( $self->_merge_result( $rows, $row ) );
+      } while (
+        $collapse
+        && do { @row = $self->cursor->next; $self->{stashed_row} = \@row if @row; }
+      );
+
+    return $rows->[0];
+
+}
+
+# _merge_result accepts an arrayref of rows objects (again, an arrayref of two elements)
+# and a row object which should be merged into the first object.
+# First we try to find out whether $row is already in $rows. If this is the case
+# we try to merge them by iteration through their relationship data. We call
+# _merge_result again on them, so they get merged.
+
+# If we don't find the $row in $rows, we append it to $rows and return undef.
+# _merge_result returns 1 otherwise (i.e. $row has been found in $rows).
+
+sub _merge_result {
+    my ( $self, $rows, $row ) = @_;
+    my ( $columns, $rels ) = @$row;
+    my $found = undef;
+    foreach my $seen (@$rows) {
+        my $match = 1;
+        foreach my $column ( keys %$columns ) {
+            if (   defined $seen->[0]->{$column} ^ defined $columns->{$column}
+                or defined $columns->{$column}
+                && $seen->[0]->{$column} ne $columns->{$column} )
+            {
+
+                $match = 0;
+                last;
             }
-          }
-          if (exists $collapse{$cur}) {
-            $target = $target->[-1];
-          }
         }
-        $target->[0] = $data;
-      } else {
-        $info->[0] = $const->{$key};
-      }
+        if ($match) {
+            $found = $seen;
+            last;
+        }
     }
-  }
+    if ($found) {
+        foreach my $rel ( keys %$rels ) {
+            my $old_rows = $found->[1]->{$rel};
+            $self->_merge_result(
+                ref $found->[1]->{$rel}->[0] eq 'HASH' ? [ $found->[1]->{$rel} ]
+                : $found->[1]->{$rel},
+                ref $rels->{$rel}->[0] eq 'HASH' ? [ $rels->{$rel}->[0], $rels->{$rel}->[1] ]
+                : $rels->{$rel}->[0]
+            );
+
+        }
 
-  return $info;
+    }
+    else {
+        push( @$rows, $row );
+        return undef;
+    }
+
+    return 1;
 }
 
+
 =head2 result_source
 
 =over 4
index fa08fae..cce1d77 100644 (file)
@@ -1407,19 +1407,7 @@ sub resolve_prefetch {
         "Can't prefetch has_many ${pre} (join cond too complex)")
         unless ref($rel_info->{cond}) eq 'HASH';
       my $dots = @{[$as_prefix =~ m/\./g]} + 1; # +1 to match the ".${as_prefix}"
-      if (my ($fail) = grep { @{[$_ =~ m/\./g]} == $dots }
-                         keys %{$collapse}) {
-        my ($last) = ($fail =~ /([^\.]+)$/);
-        carp (
-          "Prefetching multiple has_many rels ${last} and ${pre} "
-          .(length($as_prefix)
-            ? "at the same level (${as_prefix}) "
-            : "at top level "
-          )
-          . 'will explode the number of row objects retrievable via ->next or ->all. '
-          . 'Use at your own risk.'
-        );
-      }
+
       #my @col = map { (/^self\.(.+)$/ ? ("${as_prefix}.$1") : ()); }
       #              values %{$rel_info->{cond}};
       $collapse->{".${as_prefix}${pre}"} = [ $rel_source->primary_columns ];
@@ -1494,19 +1482,7 @@ sub _resolve_prefetch {
         "Can't prefetch has_many ${pre} (join cond too complex)")
         unless ref($rel_info->{cond}) eq 'HASH';
       my $dots = @{[$as_prefix =~ m/\./g]} + 1; # +1 to match the ".${as_prefix}"
-      if (my ($fail) = grep { @{[$_ =~ m/\./g]} == $dots }
-                         keys %{$collapse}) {
-        my ($last) = ($fail =~ /([^\.]+)$/);
-        carp (
-          "Prefetching multiple has_many rels ${last} and ${pre} "
-          .(length($as_prefix)
-            ? "at the same level (${as_prefix}) "
-            : "at top level "
-          )
-          . 'will explode the number of row objects retrievable via ->next or ->all. '
-          . 'Use at your own risk.'
-        );
-      }
+
       #my @col = map { (/^self\.(.+)$/ ? ("${as_prefix}.$1") : ()); }
       #              values %{$rel_info->{cond}};
       $collapse->{".${as_prefix}${pre}"} = [ $rel_source->primary_columns ];
index 7560d2c..164f58e 100644 (file)
@@ -9,7 +9,7 @@ use DBIC::SqlMakerTest;
 
 my $schema = DBICTest->init_schema();
 
-plan tests => 24;
+plan tests => 23;
 
 my $rs = $schema->resultset('CD')->search({},
     {
@@ -165,34 +165,19 @@ my $sub_rs = $rs->search ({},
   }
 );
 
-is_deeply (
-  $sub_rs->single,
-  {
-    artist => 1,
-    track_position => 2,
-    tracks =>
-      {
-        trackid => 17,
-        title => 'Apiary',
-      },
-  },
-  'columns/select/as fold properly on sub-searches',
-);
-
-TODO: {
-  local $TODO = "Multi-collapsing still doesn't work right - HRI should be getting an arrayref, not an individual hash";
-  is_deeply (
+is_deeply(
     $sub_rs->single,
     {
-      artist => 1,
-      track_position => 2,
-      tracks => [
-        {
-          trackid => 17,
-          title => 'Apiary',
-        },
-      ],
+        artist         => 1,
+        track_position => 2,
+        tracks         => [
+            {
+                trackid => 17,
+                title   => 'Apiary',
+            },
+        ],
     },
     'columns/select/as fold properly on sub-searches',
-  );
-}
+);
+
+
index 1d32dd6..79f96c6 100644 (file)
@@ -110,8 +110,8 @@ for my $index (0 .. $#hashrefinf) {
     my $datahashref = $hashrefinf[$index];
 
     is ($track->cd->artist->name, $datahashref->{name}, 'Brought back correct artist');
-    for my $col (keys %{$datahashref->{cds}{tracks}}) {
-        is ($track->get_column ($col), $datahashref->{cds}{tracks}{$col}, "Correct track '$col'");
+    for my $col (keys %{$datahashref->{cds}[0]{tracks}[0]}) {
+        is ($track->get_column ($col), $datahashref->{cds}[0]{tracks}[0]{$col}, "Correct track '$col'");
     }
 }
 
index fe30ac8..9c87919 100644 (file)
@@ -104,4 +104,4 @@ foreach my $name (keys %tests) {
     is($artwork->cd->artist->artistid, 1, $name . ', correct artist_id over cd');
     is($artwork->artwork_to_artist->first->artist->artistid, 2, $name . ', correct artist_id over A2A');
   }
-}
\ No newline at end of file
+}
index 7f97943..25e0e34 100644 (file)
@@ -87,12 +87,12 @@ for ($cd_rs->all) {
     '(
       SELECT me.cd, me.track_count, cd.cdid, cd.artist, cd.title, cd.year, cd.genreid, cd.single_track
         FROM (
-          SELECT me.cd, COUNT (me.trackid) AS track_count,
+          SELECT me.cd, COUNT (me.trackid) AS track_count
             FROM track me
             JOIN cd cd ON cd.cdid = me.cd
           WHERE ( me.cd IN ( ?, ?, ?, ?, ? ) )
           GROUP BY me.cd
-          ) as me
+          ) me
         JOIN cd cd ON cd.cdid = me.cd
       WHERE ( me.cd IN ( ?, ?, ?, ?, ? ) )
     )',
@@ -166,7 +166,7 @@ for ($cd_rs->all) {
               tracks.trackid, tracks.cd, tracks.position, tracks.title, tracks.last_updated_on, tracks.last_updated_at, tracks.small_dt,
               liner_notes.liner_id, liner_notes.notes
         FROM (
-          SELECT me.cdid, COUNT( tracks.trackid ) AS track_count, MAX( tracks.trackid ) AS maxtr,
+          SELECT me.cdid, COUNT( tracks.trackid ) AS track_count, MAX( tracks.trackid ) AS maxtr
             FROM cd me
             LEFT JOIN track tracks ON tracks.cd = me.cdid
           WHERE ( me.cdid IS NOT NULL )
@@ -217,7 +217,7 @@ for ($cd_rs->all) {
     $rs->as_query,
     '(
       SELECT me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track,
-             tags.tagid, tags.cd, tags.tag 
+             tags.tagid, tags.cd, tags.tag
         FROM (
           SELECT me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track
             FROM cd me
index 7e8b742..b9671c9 100644 (file)
@@ -1,5 +1,5 @@
 use strict;
-use warnings;  
+use warnings;
 
 use Test::More;
 use Test::Exception;
@@ -7,139 +7,79 @@ use lib qw(t/lib);
 use DBICTest;
 use IO::File;
 
-plan tests => 10;
-
 my $schema = DBICTest->init_schema();
 my $sdebug = $schema->storage->debug;
 
+#( 1 -> M + M )
+my $cd_rs = $schema->resultset('CD')->search( { 'me.title' => 'Forkful of bees' } );
+my $pr_cd_rs = $cd_rs->search( {}, { prefetch => [qw/tracks tags/], } );
 
-# once the following TODO is complete, remove the 2 warning tests immediately
-# after the TODO block
-# (the TODO block itself contains tests ensuring that the warns are removed)
-TODO: {
-    local $TODO = 'Prefetch of multiple has_many rels at the same level (currently warn to protect the clueless git)';
-
-    #( 1 -> M + M )
-    my $cd_rs = $schema->resultset('CD')->search ({ 'me.title' => 'Forkful of bees' });
-    my $pr_cd_rs = $cd_rs->search ({}, {
-        prefetch => [qw/tracks tags/],
-    });
-
-    my $tracks_rs = $cd_rs->first->tracks;
-    my $tracks_count = $tracks_rs->count;
-
-    my ($pr_tracks_rs, $pr_tracks_count);
-
-    my $queries = 0;
-    $schema->storage->debugcb(sub { $queries++ });
-    $schema->storage->debug(1);
-
-    my $o_mm_warn;
-    {
-        local $SIG{__WARN__} = sub { $o_mm_warn = shift };
-        $pr_tracks_rs = $pr_cd_rs->first->tracks;
-    };
-    $pr_tracks_count = $pr_tracks_rs->count;
-
-    ok(! $o_mm_warn, 'no warning on attempt to prefetch several same level has_many\'s (1 -> M + M)');
-
-    is($queries, 1, 'prefetch one->(has_many,has_many) ran exactly 1 query');
-    $schema->storage->debugcb (undef);
-    $schema->storage->debug ($sdebug);
+my $tracks_rs    = $cd_rs->first->tracks;
+my $tracks_count = $tracks_rs->count;
 
-    is($pr_tracks_count, $tracks_count, 'equal count of prefetched relations over several same level has_many\'s (1 -> M + M)');
-    is ($pr_tracks_rs->all, $tracks_rs->all, 'equal amount of objects returned with and without prefetch over several same level has_many\'s (1 -> M + M)');
+my ( $pr_tracks_rs, $pr_tracks_count );
 
-    #( M -> 1 -> M + M )
-    my $note_rs = $schema->resultset('LinerNotes')->search ({ notes => 'Buy Whiskey!' });
-    my $pr_note_rs = $note_rs->search ({}, {
-        prefetch => {
-            cd => [qw/tracks tags/]
-        },
-    });
+my $queries = 0;
+$schema->storage->debugcb( sub { $queries++ } );
+$schema->storage->debug(1);
 
-    my $tags_rs = $note_rs->first->cd->tags;
-    my $tags_count = $tags_rs->count;
-
-    my ($pr_tags_rs, $pr_tags_count);
-
-    $queries = 0;
-    $schema->storage->debugcb(sub { $queries++ });
-    $schema->storage->debug(1);
-
-    my $m_o_mm_warn;
-    {
-        local $SIG{__WARN__} = sub { $m_o_mm_warn = shift };
-        $pr_tags_rs = $pr_note_rs->first->cd->tags;
-    };
-    $pr_tags_count = $pr_tags_rs->count;
-
-    ok(! $m_o_mm_warn, 'no warning on attempt to prefetch several same level has_many\'s (M -> 1 -> M + M)');
-
-    is($queries, 1, 'prefetch one->(has_many,has_many) ran exactly 1 query');
-    $schema->storage->debugcb (undef);
-    $schema->storage->debug ($sdebug);
-
-    is($pr_tags_count, $tags_count, 'equal count of prefetched relations over several same level has_many\'s (M -> 1 -> M + M)');
-    is($pr_tags_rs->all, $tags_rs->all, 'equal amount of objects with and without prefetch over several same level has_many\'s (M -> 1 -> M + M)');
-}
-
-# remove this closure once the TODO above is working
+my $o_mm_warn;
+{
+    local $SIG{__WARN__} = sub { $o_mm_warn = shift };
+    $pr_tracks_rs = $pr_cd_rs->first->tracks;
+};
+$pr_tracks_count = $pr_tracks_rs->count;
+
+ok( !$o_mm_warn,
+'no warning on attempt to prefetch several same level has_many\'s (1 -> M + M)'
+);
+
+is( $queries, 1, 'prefetch one->(has_many,has_many) ran exactly 1 query' );
+$schema->storage->debugcb(undef);
+$schema->storage->debug($sdebug);
+
+is( $pr_tracks_count, $tracks_count,
+'equal count of prefetched relations over several same level has_many\'s (1 -> M + M)'
+);
+is( $pr_tracks_rs->all, $tracks_rs->all,
+'equal amount of objects returned with and without prefetch over several same level has_many\'s (1 -> M + M)'
+);
+
+#( M -> 1 -> M + M )
+my $note_rs =
+  $schema->resultset('LinerNotes')->search( { notes => 'Buy Whiskey!' } );
+my $pr_note_rs =
+  $note_rs->search( {}, { prefetch => { cd => [qw/tracks tags/] }, } );
+
+my $tags_rs    = $note_rs->first->cd->tags;
+my $tags_count = $tags_rs->count;
+
+my ( $pr_tags_rs, $pr_tags_count );
+
+$queries = 0;
+$schema->storage->debugcb( sub { $queries++ } );
+$schema->storage->debug(1);
+
+my $m_o_mm_warn;
 {
-    my $warn_re = qr/will explode the number of row objects retrievable via/;
-
-    my (@w, @dummy);
-    local $SIG{__WARN__} = sub { $_[0] =~ $warn_re ? push @w, @_ : warn @_ };
-
-    my $rs = $schema->resultset('CD')->search ({ 'me.title' => 'Forkful of bees' }, { prefetch => [qw/tracks tags/] });
-    @w = ();
-    @dummy = $rs->first;
-    is (@w, 1, 'warning on attempt prefetching several same level has_manys (1 -> M + M)');
-
-    my $rs2 = $schema->resultset('LinerNotes')->search ({ notes => 'Buy Whiskey!' }, { prefetch => { cd => [qw/tags tracks/] } });
-    @w = ();
-    @dummy = $rs2->first;
-    is (@w, 1, 'warning on attempt prefetching several same level has_manys (M -> 1 -> M + M)');
-}
-
-__END__
-The solution is to rewrite ResultSet->_collapse_result() and
-ResultSource->resolve_prefetch() to focus on the final results from the collapse
-of the data. Right now, the code doesn't treat the columns from the various
-tables as grouped entities. While there is a concept of hierarchy (so that
-prefetching down relationships does work as expected), there is no idea of what
-the final product should look like and how the various columns in the row would
-play together. So, the actual prefetch datastructure from the search would be
-very useful in working through this problem. We already have access to the PKs
-and sundry for those. So, when collapsing the search result, we know we are
-looking for 1 cd object. We also know we're looking for tracks and tags records
--independently- of each other. So, we can grab the data for tracks and data for
-tags separately, uniqueing on the PK as appropriate. Then, when we're done with
-the given cd object's datastream, we know we're good. This should work for all
-the various scenarios.
-
-My reccommendation is the row's data is preprocessed first, breaking it up into
-the data for each of the component tables. (This could be done in the single
-table case, too, but probably isn't necessary.) So, starting with something
-like:
-  my $row = {
-    t1.col1 => 1,
-    t1.col2 => 2,
-    t2.col1 => 3,
-    t2.col2 => 4,
-    t3.col1 => 5,
-    t3.col2 => 6,
-  };
-it is massaged to look something like:
-  my $row_massaged = {
-    t1 => { col1 => 1, col2 => 2 },
-    t2 => { col1 => 3, col2 => 4 },
-    t3 => { col1 => 5, col2 => 6 },
-  };
-At this point, find the stuff that's different is easy enough to do and slotting
-things into the right spot is, likewise, pretty straightforward. Instead of
-storing things in a AoH, store them in a HoH keyed on the PKs of the the table,
-then convert to an AoH after all collapsing is done.
-
-This implies that the collapse attribute can probably disappear or, at the
-least, be turned into a boolean (which is how it's used in every other place).
+    local $SIG{__WARN__} = sub { $m_o_mm_warn = shift };
+    $pr_tags_rs = $pr_note_rs->first->cd->tags;
+};
+$pr_tags_count = $pr_tags_rs->count;
+
+ok( !$m_o_mm_warn,
+'no warning on attempt to prefetch several same level has_many\'s (M -> 1 -> M + M)'
+);
+
+is( $queries, 1, 'prefetch one->(has_many,has_many) ran exactly 1 query' );
+$schema->storage->debugcb(undef);
+$schema->storage->debug($sdebug);
+
+is( $pr_tags_count, $tags_count,
+'equal count of prefetched relations over several same level has_many\'s (M -> 1 -> M + M)'
+);
+is( $pr_tags_rs->all, $tags_rs->all,
+'equal amount of objects with and without prefetch over several same level has_many\'s (M -> 1 -> M + M)'
+);
+
+done_testing;
index 1dd0829..24625ad 100644 (file)
@@ -89,4 +89,3 @@ is($artist->cds->count, 1, "count on search limiting prefetched has_many");
 # try with double limit
 my $artist2 = $use_prefetch->search({'cds.title' => { '!=' => $artist_many_cds->cds->first->title } })->slice (0,0)->next;
 is($artist2->cds->count, 2, "count on search limiting prefetched has_many");
-