Merge 'trunk' into 'pg_unqualified_schema'
Peter Rabbitson [Mon, 17 Aug 2009 09:52:49 +0000 (09:52 +0000)]
r7256@Thesaurus (orig r7253):  ribasushi | 2009-08-07 11:19:35 +0200
 r7232@Thesaurus (orig r7229):  jnapiorkowski | 2009-08-05 16:56:32 +0200
 added test for the new default force pool behavior in PK->discard_changes and cleaned up the related tests a bit to give more meaningful info
 r7233@Thesaurus (orig r7230):  jnapiorkowski | 2009-08-05 16:57:45 +0200
 opps typo in test status messages
 r7234@Thesaurus (orig r7231):  jnapiorkowski | 2009-08-05 17:03:46 +0200
 added the default attrs to solve the failing test recently commited
 r7235@Thesaurus (orig r7232):  jnapiorkowski | 2009-08-05 17:58:44 +0200
 added test to make sure you can override the default attributes to discard_changes
 r7241@Thesaurus (orig r7238):  jnapiorkowski | 2009-08-05 22:00:58 +0200
 added replication as an optional feature to make installing it easier
 r7253@Thesaurus (orig r7250):  ribasushi | 2009-08-07 11:06:41 +0200
 Streamline makefile dep handling
 r7254@Thesaurus (orig r7251):  ribasushi | 2009-08-07 11:07:14 +0200
 Switch to done_testing
 r7255@Thesaurus (orig r7252):  ribasushi | 2009-08-07 11:19:13 +0200
 Move discard_changes code to Row.pm, better docs

r7257@Thesaurus (orig r7254):  ribasushi | 2009-08-07 11:21:35 +0200
Remove merged branch
r7259@Thesaurus (orig r7256):  ribasushi | 2009-08-07 14:16:13 +0200
Fix bogus POD
r7261@Thesaurus (orig r7258):  ribasushi | 2009-08-07 17:22:58 +0200
per mst: no optional deps
r7262@Thesaurus (orig r7259):  ribasushi | 2009-08-08 17:02:39 +0200
Stop using discard_changes() in Ordered (if I knew it will be *that* complex I would not touch it)
r7265@Thesaurus (orig r7262):  ribasushi | 2009-08-08 17:49:19 +0200
 r7032@Thesaurus (orig r7031):  caelum | 2009-07-11 11:28:52 +0200
 new branch to reduce connected() calls
 r7033@Thesaurus (orig r7032):  caelum | 2009-07-11 13:07:41 +0200
 added failing test
 r7034@Thesaurus (orig r7033):  caelum | 2009-07-11 14:36:53 +0200
 minor optimization
 r7048@Thesaurus (orig r7047):  caelum | 2009-07-14 15:09:47 +0200
 substantially reduced ping count, dynamic cursors support for mssql through odbc
 r7050@Thesaurus (orig r7049):  caelum | 2009-07-14 16:06:39 +0200
 a couple more options for odbc/mssql
 r7052@Thesaurus (orig r7051):  caelum | 2009-07-15 00:14:09 +0200
 unfuck ensure_connected for odbc/mssql
 r7055@Thesaurus (orig r7054):  caelum | 2009-07-15 21:10:27 +0200
 rename _scope_identity to _identity for odbc/mssql
 r7056@Thesaurus (orig r7055):  caelum | 2009-07-16 00:41:45 +0200
 add IC::DT tests for odbc/mssql
 r7069@Thesaurus (orig r7068):  caelum | 2009-07-17 11:47:31 +0200
 don't run connection actions if ->_rebless does not connect
 r7108@Thesaurus (orig r7105):  caelum | 2009-07-24 07:26:13 +0200
 moving test to another branch
 r7110@Thesaurus (orig r7107):  caelum | 2009-07-24 07:52:33 +0200
 revert odbc/mssql code to trunk and move it to another branch
 r7111@Thesaurus (orig r7108):  caelum | 2009-07-24 08:13:35 +0200
 revert t/746mssql.t to trunk and move to another branch
 r7224@Thesaurus (orig r7221):  caelum | 2009-08-05 11:48:04 +0200
 update branch after pull
 r7225@Thesaurus (orig r7222):  ribasushi | 2009-08-05 12:09:07 +0200
 Rename last_dbh and turn it into a public method
 r7226@Thesaurus (orig r7223):  ribasushi | 2009-08-05 12:12:20 +0200
 Whoopsie - more renames
 r7227@Thesaurus (orig r7224):  ribasushi | 2009-08-05 12:32:09 +0200
 Changes and a deploy() fix
 r7228@Thesaurus (orig r7225):  ribasushi | 2009-08-05 12:36:01 +0200
 We do not count pings during deploy - they are expected
 r7229@Thesaurus (orig r7226):  ribasushi | 2009-08-05 12:49:06 +0200
 Clarify autocommit default
 r7238@Thesaurus (orig r7235):  caelum | 2009-08-05 20:39:47 +0200
 fix up txn_begin and the ping_count test
 r7263@Thesaurus (orig r7260):  ribasushi | 2009-08-08 17:40:19 +0200
 A more straightforward txn_begin fix, some more test fixes

r7270@Thesaurus (orig r7267):  ribasushi | 2009-08-09 00:34:31 +0200
 r6822@Thesaurus (orig r6821):  caelum | 2009-06-28 14:38:12 +0200
 branch
 r6825@Thesaurus (orig r6824):  caelum | 2009-06-28 14:40:37 +0200
 ->table(\"table")
 r6827@Thesaurus (orig r6826):  caelum | 2009-06-28 14:55:06 +0200
 revert
 r6829@Thesaurus (orig r6828):  caelum | 2009-06-28 15:57:40 +0200
  r5742@hlagh (orig r6819):  ribasushi | 2009-06-28 04:00:03 -0700
  The prefetch+group_by is a complex problem - branch

 r6834@Thesaurus (orig r6833):  caelum | 2009-06-28 23:24:47 +0200
 ->table(\"foo") now works
 r6835@Thesaurus (orig r6834):  caelum | 2009-06-29 03:54:31 +0200
 another test
 r6849@Thesaurus (orig r6848):  caelum | 2009-06-29 21:39:26 +0200
 separated table ref test out, changed CDTableRef to a view with less rels
 r6852@Thesaurus (orig r6851):  caelum | 2009-06-29 22:56:45 +0200
 changed CD to ->table(\"cd")
 r6853@Thesaurus (orig r6852):  caelum | 2009-06-29 23:13:48 +0200
 fix t/80unique.t
 r6857@Thesaurus (orig r6856):  caelum | 2009-06-29 23:45:19 +0200
 branch pushed, removing
 r6858@Thesaurus (orig r6857):  caelum | 2009-06-29 23:46:54 +0200
 removing debug statement
 r6860@Thesaurus (orig r6859):  ribasushi | 2009-06-30 00:03:21 +0200
 Minor fixes
 r6861@Thesaurus (orig r6860):  ribasushi | 2009-06-30 00:25:27 +0200
 This is sloppy, but sqlt is sloppy too. All tests pass now, all we really need is to intercept name() set-calls, and use a virtual view (the only legit setter is the new() call in ResultSourceProxy::Table
 r6867@Thesaurus (orig r6866):  caelum | 2009-06-30 03:34:02 +0200
 forgot to use Scalar::Util ()
 r7007@Thesaurus (orig r7006):  caelum | 2009-07-09 07:37:22 +0200
  r5766@hlagh (orig r6843):  abraxxa | 2009-06-29 02:02:17 -0700
  fixed typo in test

  r5779@hlagh (orig r6847):  ribasushi | 2009-06-29 10:09:00 -0700
  Minor Ordered optimization (don't use count)
  r5787@hlagh (orig r6855):  caelum | 2009-06-29 14:42:11 -0700
   r5451@hlagh (orig r6605):  caelum | 2009-06-10 09:23:44 -0700
   new branch to implement on_connect_call
   r5484@hlagh (orig r6633):  caelum | 2009-06-11 11:03:10 -0700
   on_connect_call implementation and set_datetime_format support for Oracle
   r5492@hlagh (orig r6641):  caelum | 2009-06-11 16:39:28 -0700
   connect_call_set_datetime_format for Oracle, I have no idea why this didn't get committed before...
   r5504@hlagh (orig r6655):  caelum | 2009-06-12 17:28:06 -0700
   finished up on_connect_call stuff
   r5507@hlagh (orig r6658):  caelum | 2009-06-13 04:03:36 -0700
   fixup _setup_connect_do, other minor cleanups
   r5508@hlagh (orig r6659):  caelum | 2009-06-13 04:35:33 -0700
   make the on_(dis)?connect_do accessors returnn the original structure
   r5509@hlagh (orig r6660):  caelum | 2009-06-13 08:31:52 -0700
   allow undef for _setup_connect_do
   r5522@hlagh (orig r6679):  caelum | 2009-06-14 09:56:40 -0700
   rename connect_do store
   r5621@hlagh (orig r6769):  caelum | 2009-06-23 07:38:33 -0700
   minor doc update
   r5628@hlagh (orig r6777):  caelum | 2009-06-23 16:36:12 -0700
   properly test nanosecond precision with oracle and datetime_setup
   r5669@hlagh (orig r6784):  caelum | 2009-06-24 10:49:25 -0700
   IC::DT does support timestamp with timezone
   r5768@hlagh (orig r6846):  caelum | 2009-06-29 08:20:32 -0700
   remove DateTime from 73oracle.t
   r5781@hlagh (orig r6849):  caelum | 2009-06-29 13:07:43 -0700
   remove the _store stuff for on_connect_do
   r5785@hlagh (orig r6853):  ribasushi | 2009-06-29 14:38:30 -0700
   Some beautification

  r5802@hlagh (orig r6870):  ribasushi | 2009-06-30 01:09:03 -0700
  Cleanup dependency handling a bit
  r5806@hlagh (orig r6874):  ribasushi | 2009-06-30 03:39:06 -0700
  Allow broken resultsource-class-derived objects to still work
  r5807@hlagh (orig r6875):  ribasushi | 2009-06-30 03:40:46 -0700
  clarify
  r5835@hlagh (orig r6877):  ash | 2009-06-30 04:48:13 -0700
  Update POD on Dynamic sub-classing

  r5837@hlagh (orig r6882):  ribasushi | 2009-06-30 08:36:38 -0700
   r6815@Thesaurus (orig r6814):  ribasushi | 2009-06-28 10:32:42 +0200
   Branch to explore double joins on search_related
   r6816@Thesaurus (orig r6815):  ribasushi | 2009-06-28 10:34:16 +0200
   Thetest case that started it all
   r6817@Thesaurus (orig r6816):  ribasushi | 2009-06-28 10:35:11 +0200
   The proposed fix (do not add an extra join if it is already present in the topmost join)
   r6818@Thesaurus (orig r6817):  ribasushi | 2009-06-28 11:04:26 +0200
   Minor omission
   r6819@Thesaurus (orig r6818):  ribasushi | 2009-06-28 11:07:33 +0200
   Adjust a couple of tests for new behavior (thus all of this might be backwards incompatible to the point of being useless):
   The counts in t/90join_torture.t are now 5*3, not 5*3*3, as a second join is not induced by search_related
   The raw sql scan in t/prefetch/standard.t is just silly, won't even try to understand it
   Just to maintain the TreeLike folding, I add a 3rd children join which was inserted by search_related before the code changes

  r5843@hlagh (orig r6888):  ribasushi | 2009-06-30 10:36:11 -0700
  Todoify test for now
  r5844@hlagh (orig r6889):  ribasushi | 2009-06-30 10:37:05 -0700
  Todoify test for now (2)
  r5846@hlagh (orig r6891):  ribasushi | 2009-06-30 10:52:31 -0700
  Todoify test for now (3)
  r5850@hlagh (orig r6902):  ribasushi | 2009-06-30 23:46:12 -0700
  Fixed deadlock test
  r5851@hlagh (orig r6903):  ribasushi | 2009-07-01 03:22:00 -0700
  Clarify exception text
  r5854@hlagh (orig r6906):  ribasushi | 2009-07-01 04:23:46 -0700
   r6821@Thesaurus (orig r6820):  ribasushi | 2009-06-28 13:09:11 +0200
   Branch for prefetch+group play
   r6823@Thesaurus (orig r6822):  ribasushi | 2009-06-28 14:38:36 +0200
   Normalize group_by
   r6824@Thesaurus (orig r6823):  ribasushi | 2009-06-28 14:39:54 +0200
   Proper prefetch+group test
   r6826@Thesaurus (orig r6825):  ribasushi | 2009-06-28 14:42:48 +0200
   Whoops
   r6828@Thesaurus (orig r6827):  ribasushi | 2009-06-28 15:06:57 +0200
   Lose the literal sql bits - castaway is right it's silly to support those
   r6833@Thesaurus (orig r6832):  ribasushi | 2009-06-28 22:38:43 +0200
   Rogue comments
   r6837@Thesaurus (orig r6836):  ribasushi | 2009-06-29 09:44:25 +0200
   A couple of test fixes
   r6838@Thesaurus (orig r6837):  ribasushi | 2009-06-29 09:46:13 +0200
   Support for -select/-as in SQLAHacks field selection
   r6839@Thesaurus (orig r6838):  ribasushi | 2009-06-29 09:49:53 +0200
   This is tested elsewhere
   r6840@Thesaurus (orig r6839):  ribasushi | 2009-06-29 09:50:43 +0200
   This is tested elsewhere (2)
   r6841@Thesaurus (orig r6840):  ribasushi | 2009-06-29 10:07:09 +0200
   Test cleanups
   r6842@Thesaurus (orig r6841):  ribasushi | 2009-06-29 10:11:13 +0200
   Most of the grouped prefetch solution
   r6843@Thesaurus (orig r6842):  ribasushi | 2009-06-29 10:14:45 +0200
   clearer
   r6845@Thesaurus (orig r6844):  ribasushi | 2009-06-29 12:05:37 +0200
   And score! (all works)
   r6882@Thesaurus (orig r6881):  ribasushi | 2009-06-30 16:23:06 +0200
   rs->get_column now properly recognizes prefetch and collapses if at all possible
   r6886@Thesaurus (orig r6885):  ribasushi | 2009-06-30 17:39:58 +0200
   Whoops

  r5857@hlagh (orig r6909):  ribasushi | 2009-07-01 04:27:15 -0700
  Optimize set_column on uninserted objects
  r5867@hlagh (orig r6920):  caelum | 2009-07-01 08:40:32 -0700
   r5859@hlagh (orig r6912):  caelum | 2009-07-01 06:21:30 -0700
   new connected() for dbd::sybase users
   r5860@hlagh (orig r6913):  caelum | 2009-07-01 06:25:46 -0700
   add a couple of dbd::sybase reconnection tests
   r5861@hlagh (orig r6914):  caelum | 2009-07-01 06:35:07 -0700
   better connection test
   r5862@hlagh (orig r6915):  caelum | 2009-07-01 06:45:05 -0700
   use dbh->do for connected instead of prepare_cached
   r5863@hlagh (orig r6916):  ribasushi | 2009-07-01 06:55:21 -0700
   Segfault
   r5864@hlagh (orig r6917):  caelum | 2009-07-01 07:03:22 -0700
   use ->do instead of ->prepare_cached in oracle's connected() too
   r5865@hlagh (orig r6918):  caelum | 2009-07-01 08:20:52 -0700
   fix segfault with old DBD::Sybase
   r5866@hlagh (orig r6919):  caelum | 2009-07-01 08:39:18 -0700
   move connection tests into _ping()

  r5873@hlagh (orig r6923):  ijw | 2009-07-01 10:34:32 -0700
  Added a test for a resultset to related-resultset join for 0 related records
  r5874@hlagh (orig r6927):  ijw | 2009-07-01 11:04:16 -0700
  Additional tests on prefetch - illustrates the bug with left-join has_many (NULL row returned) and the one that results from the trivial fix (prefetch gives no artist)
  r5876@hlagh (orig r6931):  ribasushi | 2009-07-01 23:08:33 -0700
  Another candidate for somethingawful.com (fix left join-ed count)
  r5877@hlagh (orig r6933):  ribasushi | 2009-07-02 00:04:13 -0700
  Changelog
  r5878@hlagh (orig r6934):  ribasushi | 2009-07-02 02:23:48 -0700
  cleanup
  r5879@hlagh (orig r6935):  ijw | 2009-07-02 03:41:01 -0700
  Check fetched rows == count for related resultsets
  r5880@hlagh (orig r6936):  ijw | 2009-07-02 03:43:47 -0700
  Confirm prefetch doesn't affect main row fetch, and main row fetch works with and without counting
  r5881@hlagh (orig r6937):  ribasushi | 2009-07-02 03:52:51 -0700
  More fail (fix is known but needs work)
  r5882@hlagh (orig r6938):  ribasushi | 2009-07-02 04:07:22 -0700
  And more fail
  r5883@hlagh (orig r6939):  ribasushi | 2009-07-02 04:16:46 -0700
  These tests are in prefetch/count.t
  r5884@hlagh (orig r6940):  ribasushi | 2009-07-02 04:38:31 -0700
  cleanup
  r5885@hlagh (orig r6941):  ribasushi | 2009-07-02 04:38:49 -0700
  Solve more prefetch inflation crap
  r5886@hlagh (orig r6942):  ribasushi | 2009-07-02 04:47:41 -0700
  Make the code readable
  r5887@hlagh (orig r6943):  ribasushi | 2009-07-02 06:52:35 -0700
  Everything works, just need to fix join-path chaining over search_related (to guard against obscure db quirks)
  r5889@hlagh (orig r6945):  caelum | 2009-07-02 12:06:32 -0700
  add sybase reconnect test
  r5891@hlagh (orig r6947):  ribasushi | 2009-07-02 13:20:21 -0700
  Last part of the join handling puzzle
  r5894@hlagh (orig r6950):  ribasushi | 2009-07-02 15:14:50 -0700
   r6360@Thesaurus (orig r6359):  arcanez | 2009-05-21 20:18:52 +0200
   branch to work on prefetch/select
   r6361@Thesaurus (orig r6360):  arcanez | 2009-05-21 20:32:46 +0200
   failing test
   r6373@Thesaurus (orig r6372):  ribasushi | 2009-05-22 11:07:26 +0200
   Simplify unresolvable test by arcanez
   r6905@Thesaurus (orig r6904):  ribasushi | 2009-07-01 12:54:03 +0200
   Extend test
   r6950@Thesaurus (orig r6949):  ribasushi | 2009-07-03 00:14:09 +0200
   Apparent fix - simply delay the in_storage flagging of the main object until all prefetched objects are inflated. The rest of the changes are just cosmetics, preparing for the collapse_result rewrite

  r5896@hlagh (orig r6952):  ribasushi | 2009-07-02 15:17:22 -0700
  Changes
  r5909@hlagh (orig r6964):  ribasushi | 2009-07-03 04:19:27 -0700
  Add set_ansi_mode on_connect_call for mysql
  Also switch to _do_query instead of plain dbh->do (shows up in the trace)
  r5910@hlagh (orig r6965):  ribasushi | 2009-07-03 04:37:06 -0700
  Capitalize mysql commands
  r5911@hlagh (orig r6966):  ribasushi | 2009-07-03 06:07:49 -0700
  Double an existing might_have test as has_one
  r5912@hlagh (orig r6967):  ribasushi | 2009-07-03 07:36:32 -0700
  Extra test to demonstrate has_one working, and a POD clarification of multicreate
  r5917@hlagh (orig r6972):  ribasushi | 2009-07-03 11:20:42 -0700
   r6554@Thesaurus (orig r6553):  frew | 2009-06-09 00:06:42 +0200
   branch for mssql top issues
   r6572@Thesaurus (orig r6571):  frew | 2009-06-09 23:18:46 +0200
   more tests for SQL Server!
   r6573@Thesaurus (orig r6572):  frew | 2009-06-09 23:49:10 +0200
   Added AmbiguousGlob.pm for silly servers like mssql and mysql.  See docs for more info
   r6574@Thesaurus (orig r6573):  frew | 2009-06-09 23:55:22 +0200
   fix plan
   r6602@Thesaurus (orig r6601):  frew | 2009-06-10 17:03:30 +0200
   more failing tests
   r6608@Thesaurus (orig r6607):  frew | 2009-06-10 20:05:53 +0200
   don't use eval!
   r6610@Thesaurus (orig r6609):  frew | 2009-06-10 20:07:49 +0200
   beginning of DWIM for IDENTITY_INSERT
   r6628@Thesaurus (orig r6627):  frew | 2009-06-11 18:13:02 +0200
   still busted :-(
   r6631@Thesaurus (orig r6630):  frew | 2009-06-11 19:39:00 +0200
   general function to go from column names and ident to result source
   r6632@Thesaurus (orig r6631):  frew | 2009-06-11 19:40:11 +0200
   Use new _resolve_column_sources method and begin insert_bulk method
   r6635@Thesaurus (orig r6634):  frew | 2009-06-11 20:12:38 +0200
   updated _resolve_column_source to _resolve_column_info as per ribasushi's suggestion
   r6650@Thesaurus (orig r6649):  frew | 2009-06-12 17:13:32 +0200
   Now I just need to check if the actual values are set...
   r6651@Thesaurus (orig r6650):  frew | 2009-06-12 17:26:53 +0200
   Insert Identity works!
   r6652@Thesaurus (orig r6651):  frew | 2009-06-12 17:34:13 +0200
   silly warns.
   r6684@Thesaurus (orig r6683):  frew | 2009-06-15 16:49:00 +0200
   failing test
   r6686@Thesaurus (orig r6685):  ribasushi | 2009-06-15 18:10:26 +0200
   make all resolved attrs visible to sqla
   r6698@Thesaurus (orig r6697):  ribasushi | 2009-06-17 02:31:37 +0200
   Half way working stuff, needs a LOT of tweaking still
   r6729@Thesaurus (orig r6728):  ribasushi | 2009-06-19 19:49:27 +0200
   Merge badness
   r6730@Thesaurus (orig r6729):  ribasushi | 2009-06-19 19:49:40 +0200
   fix eol
   r6731@Thesaurus (orig r6730):  ribasushi | 2009-06-19 19:55:47 +0200
   augment inheritance
   r6735@Thesaurus (orig r6734):  ribasushi | 2009-06-20 10:34:42 +0200
   Maybe I've nailed it
   r6746@Thesaurus (orig r6745):  ribasushi | 2009-06-20 23:53:55 +0200
   Test and merge fixes
   r6747@Thesaurus (orig r6746):  ribasushi | 2009-06-21 00:01:09 +0200
   Really fix tests
   r6748@Thesaurus (orig r6747):  ribasushi | 2009-06-21 00:01:54 +0200
   Really fix tests
   r6749@Thesaurus (orig r6748):  ribasushi | 2009-06-21 00:18:33 +0200
   Now really final
   r6750@Thesaurus (orig r6749):  ribasushi | 2009-06-21 00:22:23 +0200
   whoops
   r6751@Thesaurus (orig r6750):  ribasushi | 2009-06-21 00:42:18 +0200
   That should be all
   r6752@Thesaurus (orig r6751):  ribasushi | 2009-06-21 08:54:00 +0200
   Make sure quoting works
   r6755@Thesaurus (orig r6754):  ribasushi | 2009-06-21 15:21:23 +0200
   Groundwork for sanification of the toplimit test
   r6863@Thesaurus (orig r6862):  ribasushi | 2009-06-30 01:13:49 +0200
   Make sure storage classes use c3, just like the rest of dbic (tested on 5.8 as well)
   r6869@Thesaurus (orig r6868):  ribasushi | 2009-06-30 09:53:27 +0200
   Some fixes after review
   r6874@Thesaurus (orig r6873):  ribasushi | 2009-06-30 11:54:34 +0200
   Fix borked next invocation
   r6896@Thesaurus (orig r6895):  frew | 2009-06-30 21:38:26 +0200
   silly misspells and trailing whitespace
   r6955@Thesaurus (orig r6954):  ribasushi | 2009-07-03 01:21:28 +0200
   Some hack consolidation
   r6962@Thesaurus (orig r6961):  ribasushi | 2009-07-03 12:06:57 +0200
   Fix some mssql shortcommings when confronted with the new subequeried prefetch sql
   r6963@Thesaurus (orig r6962):  ribasushi | 2009-07-03 12:47:57 +0200
   Ask for newer DBD::Pg in author mode, suggest the newer version otherwise (proper array support). Make test more resilient as well
   r6964@Thesaurus (orig r6963):  ribasushi | 2009-07-03 12:49:16 +0200
   Switch to C3 mro throughout the ::Storage hierarchy (DBIx::Class brings in MRO::Compat, and all ::Storage's are based on it, tested on 5.8
   r6969@Thesaurus (orig r6968):  ribasushi | 2009-07-03 19:54:04 +0200
   Duh
   r6970@Thesaurus (orig r6969):  frew | 2009-07-03 19:59:48 +0200
   fix tests for new codez
   r6971@Thesaurus (orig r6970):  ribasushi | 2009-07-03 20:18:53 +0200
   detabify
   r6972@Thesaurus (orig r6971):  ribasushi | 2009-07-03 20:20:07 +0200
   changes

  r5920@hlagh (orig r6979):  ribasushi | 2009-07-04 02:34:08 -0700
  Hide devel documentation from the indexer
  r5921@hlagh (orig r6980):  ribasushi | 2009-07-04 02:37:25 -0700
  Add set_ansi_mode POD
  r5922@hlagh (orig r6981):  ribasushi | 2009-07-04 02:45:24 -0700
  Backout mysql changes for further polishing
  r5925@hlagh (orig r6984):  ribasushi | 2009-07-04 03:08:16 -0700
  Missing newline
  r5926@hlagh (orig r6985):  ribasushi | 2009-07-04 03:11:18 -0700
  typo
  r5927@hlagh (orig r6986):  ribasushi | 2009-07-04 03:40:47 -0700
  Fix POD
  r5928@hlagh (orig r6987):  ribasushi | 2009-07-04 04:09:39 -0700
  todos are shorter now
  r5929@hlagh (orig r6989):  castaway | 2009-07-05 13:00:55 -0700
  Added Pod::Inherit use to Makefile.PL at author-time, comments/suggestions as to whether its too "noisy" welcome.

  r5930@hlagh (orig r6990):  ribasushi | 2009-07-05 15:06:52 -0700
  Couple of makefile fixes:
  use is compile time, use require
  recommends is for distro maintainers only, push the dependency into the authors hash (it is not to be executed by mere mortals)

  r5931@hlagh (orig r6991):  ribasushi | 2009-07-05 15:55:36 -0700
  Forgotten pod exclusions
  r5932@hlagh (orig r6992):  ribasushi | 2009-07-05 16:07:05 -0700
  Temporarily backout Pod::Inherit changes
  r5933@hlagh (orig r6993):  ribasushi | 2009-07-05 16:10:22 -0700
  Put Pod::Inherit stuff back after proper copy

 r7027@Thesaurus (orig r7026):  caelum | 2009-07-10 23:25:56 +0200
  r5941@hlagh (orig r7009):  ribasushi | 2009-07-09 03:45:02 -0700
   r6995@Thesaurus (orig r6994):  ribasushi | 2009-07-06 01:12:57 +0200
   Where 08108 will come from

 r7029@Thesaurus (orig r7028):  caelum | 2009-07-10 23:59:31 +0200
  r5959@hlagh (orig r7027):  caelum | 2009-07-10 14:56:57 -0700
  fix PodInherit call in Makefile.PL

 r7067@Thesaurus (orig r7066):  caelum | 2009-07-17 10:18:24 +0200
  r5961@hlagh (orig r7029):  robkinyon | 2009-07-10 18:03:07 -0400
  Applied patch from kados regarding use of a DateTime::Format class to validate
  r5962@hlagh (orig r7030):  caelum | 2009-07-11 05:26:40 -0400
  reword IC::DT doc patch
  r6009@hlagh (orig r7037):  dandv | 2009-07-13 08:06:08 -0400
  PK::Auto has moved into Core since 2007
  r6010@hlagh (orig r7038):  dandv | 2009-07-13 08:15:13 -0400
  Fixed has_many example in Intro.pod
  r6011@hlagh (orig r7039):  dandv | 2009-07-13 16:58:45 -0400
  Fixed run-on sentences in FAQ
  r6012@hlagh (orig r7040):  dandv | 2009-07-13 17:18:11 -0400
  Minor POD fixes in Example.pod
  r6013@hlagh (orig r7041):  dandv | 2009-07-13 17:48:18 -0400
  Favored using ->single to get the topmost result over less readable ->slice(0)
  r6014@hlagh (orig r7042):  dandv | 2009-07-13 18:56:31 -0400
  Minor POD fixes in Cookbook
  r6015@hlagh (orig r7045):  ribasushi | 2009-07-14 07:30:55 -0400
  Minor logic cleanup
  r6016@hlagh (orig r7046):  ribasushi | 2009-07-14 08:07:11 -0400
  grouped prefetch fix
  r6023@hlagh (orig r7053):  ijw | 2009-07-15 12:55:35 -0400
  Added SQLA link for more comprehensive documentation of order_by options available
  r6026@hlagh (orig r7056):  caelum | 2009-07-15 18:54:22 -0400
  add "smalldatetime" support to IC::DT
  r6029@hlagh (orig r7059):  ribasushi | 2009-07-16 00:29:41 -0400
   r7013@Thesaurus (orig r7012):  jnapiorkowski | 2009-07-09 17:00:22 +0200
   new branch
   r7014@Thesaurus (orig r7013):  jnapiorkowski | 2009-07-09 20:06:44 +0200
   changed the way transactions are detected for replication to work with the standard way to do this, minor doc updates, fix to the force pool so you can force a particular slave, changes to the way the debugging is created
   r7015@Thesaurus (orig r7014):  jnapiorkowski | 2009-07-09 20:17:03 +0200
   more changes to the way debug output works
   r7016@Thesaurus (orig r7015):  jnapiorkowski | 2009-07-09 22:26:47 +0200
   big update to the test suite so that we now check to make sure the storage that was expected was actually used
   r7017@Thesaurus (orig r7016):  jnapiorkowski | 2009-07-09 23:23:37 +0200
   set correct number of tests, changed the debuggin output to not warn on DDL, minor change to a test resultclass so we can deploy to mysql properly
   r7018@Thesaurus (orig r7017):  jnapiorkowski | 2009-07-09 23:26:59 +0200
   corrected the number of skipped tests
   r7019@Thesaurus (orig r7018):  jnapiorkowski | 2009-07-09 23:52:22 +0200
   fixed test resultclass formatting, added a few more DBIC::Storage::DBI methods that I might need to delegate.
   r7020@Thesaurus (orig r7019):  jnapiorkowski | 2009-07-10 01:23:07 +0200
   some documention updates and changed the way we find paths for the sqlite dbfiles to use File::Spec, which I hope will solve some of the Win32 error messages
   r7023@Thesaurus (orig r7022):  jnapiorkowski | 2009-07-10 18:00:38 +0200
   pod cleanup, fixed broken pod links, and new Introduction pod
   r7024@Thesaurus (orig r7023):  jnapiorkowski | 2009-07-10 19:10:57 +0200
   updated Changes file to reflect work completed
   r7025@Thesaurus (orig r7024):  jnapiorkowski | 2009-07-10 19:37:53 +0200
   a few more Moose Type related fixes and added diag to the replication test to report the moose and types version used, to help us debug some of the moose related errors being reported
   r7058@Thesaurus (orig r7057):  ribasushi | 2009-07-16 06:28:44 +0200
   A couple of typos, and general whitespace cleanup (ick)

  r6031@hlagh (orig r7062):  jnapiorkowski | 2009-07-16 11:03:32 -0400
  increased Moose version requirements due to changes in the way type constraints get validated, which is not backwardly compatible
  r6032@hlagh (orig r7063):  dandv | 2009-07-16 21:37:28 -0400
  Minor POD grammar: it's -> its where appropriate

 r7105@Thesaurus (orig r7102):  caelum | 2009-07-24 06:34:56 +0200
  r6075@hlagh (orig r7074):  tomboh | 2009-07-20 12:20:37 -0400
  Fix POD changes from r7040.
  r6081@hlagh (orig r7077):  norbi | 2009-07-20 18:59:30 -0400

  r6082@hlagh (orig r7078):  norbi | 2009-07-20 18:59:58 -0400
   r7232@vger:  mendel | 2009-07-21 00:58:12 +0200
   Fixed documentation and added test for the "Arbitrary SQL through a custom ResultSource" Cookbook alternate (subclassing) recipe.

  r6083@hlagh (orig r7079):  norbi | 2009-07-20 19:05:32 -0400
   r7235@vger:  mendel | 2009-07-21 01:05:18 +0200
   Fixed 'typo' (removed a word that I left there by accident).

  r6084@hlagh (orig r7080):  norbi | 2009-07-21 04:06:21 -0400
   r7237@vger:  mendel | 2009-07-21 10:06:05 +0200
   Fixing what my svk client screwed up.

  r6085@hlagh (orig r7081):  caelum | 2009-07-21 10:51:55 -0400
  update Storage::Replicated prereqs
  r6086@hlagh (orig r7082):  caelum | 2009-07-21 12:16:34 -0400
  show Oracle datetime_setup alter session statements in debug output
  r6088@hlagh (orig r7085):  ribasushi | 2009-07-21 21:50:57 -0400
  Lazy folks do not run the whole test suite before merging >:(
  r6287@hlagh (orig r7097):  caelum | 2009-07-23 14:14:11 -0400
   r6092@hlagh (orig r7090):  caelum | 2009-07-23 08:24:39 -0400
   new branch for fixing the MONEY type in MSSQL
   r6093@hlagh (orig r7091):  caelum | 2009-07-23 08:34:01 -0400
   add test
   r6283@hlagh (orig r7093):  caelum | 2009-07-23 10:31:08 -0400
   fix money columns
   r6284@hlagh (orig r7094):  caelum | 2009-07-23 10:34:06 -0400
   minor change
   r6285@hlagh (orig r7095):  caelum | 2009-07-23 11:01:37 -0400
   add test for updating money value to NULL
   r6286@hlagh (orig r7096):  caelum | 2009-07-23 14:09:26 -0400
   add money type tests to dbd::sybase+mssql tests

 r7135@Thesaurus (orig r7132):  caelum | 2009-07-28 19:10:40 +0200
  r6365@hlagh (orig r7126):  caelum | 2009-07-27 20:03:47 -0400
  add postgres "timestamp without time zone" support

 r7244@Thesaurus (orig r7241):  caelum | 2009-08-06 17:12:49 +0200
 add warning for custom resultsources through ->name(SCALARREF) on ->deploy
 r7245@Thesaurus (orig r7242):  caelum | 2009-08-06 17:54:33 +0200
 improve the ->name(REF) warning code
 r7268@Thesaurus (orig r7265):  ribasushi | 2009-08-09 00:23:24 +0200
 Clarify POD and cleanup the ->name-hack warning
 r7269@Thesaurus (orig r7266):  ribasushi | 2009-08-09 00:34:09 +0200
 Fix a corner case and improve comments

r7279@Thesaurus (orig r7276):  ribasushi | 2009-08-09 15:25:34 +0200
 r6535@Thesaurus (orig r6534):  ribasushi | 2009-06-06 11:12:03 +0200
 Let's try again
 r6536@Thesaurus (orig r6535):  ribasushi | 2009-06-06 11:32:00 +0200
 Two failing MC tests
 r6624@Thesaurus (orig r6623):  ribasushi | 2009-06-11 16:54:09 +0200
 Another multicreate failing test - has_many should not do find_or_create
 r6625@Thesaurus (orig r6624):  ribasushi | 2009-06-11 16:54:49 +0200
  r6538@Thesaurus (orig r6537):  ribasushi | 2009-06-07 23:07:55 +0200
  Fix for mysql subquery problem
  r6539@Thesaurus (orig r6538):  ribasushi | 2009-06-07 23:36:43 +0200
  Make empty/default inserts use standard SQL
  r6540@Thesaurus (orig r6539):  ribasushi | 2009-06-08 00:59:21 +0200
  Add mysql empty insert SQL override
  Make SQLAHacks parts loadable at runtime via ensure_class_loaded
  r6541@Thesaurus (orig r6540):  ribasushi | 2009-06-08 01:03:04 +0200
  Make podcoverage happy
  r6542@Thesaurus (orig r6541):  ribasushi | 2009-06-08 01:24:06 +0200
  Fix find_or_new/create to stop returning random rows when default value insert is requested
  r6543@Thesaurus (orig r6542):  ribasushi | 2009-06-08 11:36:56 +0200
  Simply order_by/_virtual_order_by handling
  r6553@Thesaurus (orig r6552):  ribasushi | 2009-06-08 23:56:41 +0200
  duh
  r6557@Thesaurus (orig r6556):  ash | 2009-06-09 12:20:34 +0200
  Addjust bug to show problem with rows => 1 + child rel

  r6558@Thesaurus (orig r6557):  ribasushi | 2009-06-09 13:12:46 +0200
  Require a recent bugfixed Devel::Cycle
  r6560@Thesaurus (orig r6559):  ash | 2009-06-09 15:07:30 +0200
  Make IC::DT extra warning state the column name too

  r6575@Thesaurus (orig r6574):  ribasushi | 2009-06-10 00:19:48 +0200
  AuthorCheck fixes
  r6579@Thesaurus (orig r6578):  ribasushi | 2009-06-10 00:52:17 +0200
   r6522@Thesaurus (orig r6521):  ribasushi | 2009-06-05 19:27:55 +0200
   New branch to try resultsource related stuff
   r6545@Thesaurus (orig r6544):  ribasushi | 2009-06-08 13:00:54 +0200
   First stab at adding resultsources to each join in select - works won-der-ful-ly
   r6546@Thesaurus (orig r6545):  ribasushi | 2009-06-08 13:14:08 +0200
   Commit failing test and thoughts on search arg deflation
   r6576@Thesaurus (orig r6575):  ribasushi | 2009-06-10 00:31:55 +0200
   Todoify DT in search deflation test until after 0.09
   r6577@Thesaurus (orig r6576):  ribasushi | 2009-06-10 00:48:07 +0200
   Factor out the $ident resolver

  r6581@Thesaurus (orig r6580):  ribasushi | 2009-06-10 01:21:50 +0200
  Move as_query out of the cursor
  r6582@Thesaurus (orig r6581):  ribasushi | 2009-06-10 01:27:19 +0200
  Think before commit
  r6583@Thesaurus (orig r6582):  ribasushi | 2009-06-10 09:37:19 +0200
  Clarify and disable rows/prefetch test - fix is easy, but architecturally unsound - need more time
  r6591@Thesaurus (orig r6590):  ribasushi | 2009-06-10 13:33:37 +0200
   r6544@Thesaurus (orig r6543):  ribasushi | 2009-06-08 11:44:59 +0200
   Attempt to figure out why do we repeat joins on complex search_related
   r6586@Thesaurus (orig r6585):  ribasushi | 2009-06-10 11:22:05 +0200
   Move the rs preservation test to a more suitable place
   r6589@Thesaurus (orig r6588):  ribasushi | 2009-06-10 13:15:48 +0200
   Finally commit trully failing test
   r6590@Thesaurus (orig r6589):  ribasushi | 2009-06-10 13:33:14 +0200
   Duh, this was a pretty simple bug

  r6593@Thesaurus (orig r6592):  ribasushi | 2009-06-10 13:43:31 +0200
  What was I thinking - resultsource does not have an ->alias
  r6598@Thesaurus (orig r6597):  ribasushi | 2009-06-10 14:48:39 +0200
  Adjust changelog
  r6601@Thesaurus (orig r6600):  ribasushi | 2009-06-10 15:50:43 +0200
  Release 0.08104
  r6615@Thesaurus (orig r6614):  ribasushi | 2009-06-11 14:29:48 +0200
  Move around inflation tests
  r6616@Thesaurus (orig r6615):  ribasushi | 2009-06-11 14:32:07 +0200
  explicitly remove manifest on author mode make
  r6617@Thesaurus (orig r6616):  ribasushi | 2009-06-11 15:02:41 +0200
  IC::DT changes:
  Switch SQLite storage to DT::F::SQLite
  Fix exception when undef_if_invalid and timezone are both set on a column
  Split t/89inflate_datetime into separate tests
  Adjust makefile author dependencies
  r6618@Thesaurus (orig r6617):  ribasushi | 2009-06-11 15:07:41 +0200
  Move file_column test to inflate/ too
  r6621@Thesaurus (orig r6620):  ribasushi | 2009-06-11 16:16:20 +0200
   r5713@Thesaurus (orig r5712):  ribasushi | 2009-03-08 23:53:28 +0100
   Branch for datatype-aware updates
   r6604@Thesaurus (orig r6603):  ribasushi | 2009-06-10 18:08:25 +0200
   Test for type-aware update
   r6607@Thesaurus (orig r6606):  ribasushi | 2009-06-10 19:57:04 +0200
   Datatype aware update works
   r6609@Thesaurus (orig r6608):  ribasushi | 2009-06-10 20:06:40 +0200
   Whoops
   r6614@Thesaurus (orig r6613):  ribasushi | 2009-06-11 09:23:54 +0200
   Add attribute doc
   r6620@Thesaurus (orig r6619):  ribasushi | 2009-06-11 16:15:53 +0200
   Use equality, not comparison

  r6623@Thesaurus (orig r6622):  ribasushi | 2009-06-11 16:21:53 +0200
  Changes

 r6626@Thesaurus (orig r6625):  ribasushi | 2009-06-11 17:00:06 +0200
 Adjust renamed relationship
 r6646@Thesaurus (orig r6645):  ribasushi | 2009-06-12 09:00:02 +0200
 This is not update_or_create - create any non-belongs_to without asking many questions
 r7194@Thesaurus (orig r7191):  ribasushi | 2009-08-04 15:20:35 +0200
 fix merge fallout
 r7195@Thesaurus (orig r7192):  ribasushi | 2009-08-04 15:39:05 +0200
 Remove bogus test - the real test is in t/multi_create/has_many.t
 r7196@Thesaurus (orig r7193):  ribasushi | 2009-08-04 15:48:33 +0200
 Separate the diamond MC test
 Use the new Test::More's no_plan ability
 r7274@Thesaurus (orig r7271):  ribasushi | 2009-08-09 14:39:29 +0200
 Fix an arcane case with pk==fk tables (use the relationship direction specification if it is available
 r7275@Thesaurus (orig r7272):  ribasushi | 2009-08-09 14:45:20 +0200
 Optimize handling of {_rel_in_storage}, greatly reducing the amounf ot find_or_create calls (as indicated by the TODOs in t/multi_create/reentrance_count.t
 r7277@Thesaurus (orig r7274):  ribasushi | 2009-08-09 15:23:24 +0200
 Comment and todoify remaining test - too much of an undertaking / needs discussion
 r7278@Thesaurus (orig r7275):  ribasushi | 2009-08-09 15:24:58 +0200
 newline

r7282@Thesaurus (orig r7279):  ribasushi | 2009-08-09 16:17:03 +0200
Whoops, missed a line
r7283@Thesaurus (orig r7280):  mo | 2009-08-09 19:10:56 +0200
added TODO test: call accessors when create()ing a row
r7284@Thesaurus (orig r7281):  ribasushi | 2009-08-10 08:01:59 +0200
Fix bogus test
r7291@Thesaurus (orig r7288):  caelum | 2009-08-10 10:13:19 +0200
make _determine_driver more reentrant
r7297@Thesaurus (orig r7294):  michaelr | 2009-08-10 22:40:33 +0200
Added exception when resultset called without an argument

r7298@Thesaurus (orig r7295):  andyg | 2009-08-11 00:34:13 +0200
Add failing test for RT 47779, group_by as a scalar ref
r7301@Thesaurus (orig r7298):  ribasushi | 2009-08-11 09:52:03 +0200
Extra intro pod
r7302@Thesaurus (orig r7299):  mo | 2009-08-11 13:20:37 +0200
removed TODO test
r7303@Thesaurus (orig r7300):  ribasushi | 2009-08-11 14:16:28 +0200
Sanify group_by handling in complex prefetch rewrites
r7304@Thesaurus (orig r7301):  ribasushi | 2009-08-11 17:52:49 +0200
cleanup
r7305@Thesaurus (orig r7302):  ribasushi | 2009-08-11 19:40:59 +0200
Whitespace
r7306@Thesaurus (orig r7303):  ribasushi | 2009-08-11 20:00:11 +0200
Fix an obscure regression when inserting an object with a serialize-deflating column set
r7314@Thesaurus (orig r7311):  ribasushi | 2009-08-12 16:11:24 +0200
Remove needless inflate in Ordered
r7315@Thesaurus (orig r7312):  ribasushi | 2009-08-12 16:13:48 +0200
Remove leftovers from frew's tests
r7316@Thesaurus (orig r7313):  ribasushi | 2009-08-12 16:16:08 +0200
Grrrr
r7317@Thesaurus (orig r7314):  ribasushi | 2009-08-13 07:40:44 +0200
Caelum was right to make _get_dbh private - reverting (and some code refactoring)
r7318@Thesaurus (orig r7315):  ribasushi | 2009-08-13 07:41:43 +0200
Add a db/txn_do retry debugger (interesting results)
r7319@Thesaurus (orig r7316):  ribasushi | 2009-08-13 07:42:51 +0200
Adjust the storage DESTROY and the tests to accomodate the new global RaiseError=1
r7320@Thesaurus (orig r7317):  ribasushi | 2009-08-13 08:12:08 +0200
Last bit
r7322@Thesaurus (orig r7319):  ribasushi | 2009-08-17 11:09:39 +0200
Allow select AS specification for functions only via the -as hash-key (no pod yet)
r7323@Thesaurus (orig r7320):  ribasushi | 2009-08-17 11:41:08 +0200
Cookbook entry for -as and syntax tests
r7324@Thesaurus (orig r7321):  ribasushi | 2009-08-17 11:51:21 +0200
Changes

52 files changed:
Changes
Makefile.PL
lib/DBIx/Class/Manual/Cookbook.pod
lib/DBIx/Class/Manual/Intro.pod
lib/DBIx/Class/Ordered.pm
lib/DBIx/Class/PK.pm
lib/DBIx/Class/ResultSet.pm
lib/DBIx/Class/ResultSource.pm
lib/DBIx/Class/ResultSourceProxy/Table.pm
lib/DBIx/Class/Row.pm
lib/DBIx/Class/SQLAHacks.pm
lib/DBIx/Class/SQLAHacks/MySQL.pm
lib/DBIx/Class/Schema.pm
lib/DBIx/Class/Storage/DBI.pm
lib/DBIx/Class/Storage/DBI/MSSQL.pm
lib/DBIx/Class/Storage/DBI/ODBC.pm
lib/DBIx/Class/Storage/DBI/ODBC/Microsoft_SQL_Server.pm
lib/DBIx/Class/Storage/DBI/Oracle.pm
lib/DBIx/Class/Storage/DBI/Oracle/Generic.pm
lib/DBIx/Class/Storage/DBI/Pg.pm
lib/DBIx/Class/Storage/DBI/Sybase.pm
lib/DBIx/Class/Storage/DBI/Sybase/Base.pm
lib/DBIx/Class/Storage/DBI/Sybase/Microsoft_SQL_Server.pm
lib/DBIx/Class/Storage/DBI/mysql.pm
lib/SQL/Translator/Parser/DBIx/Class.pm
t/19quotes.t
t/19quotes_newstyle.t
t/60core.t
t/745db2.t
t/746db2_400.t
t/76select.t
t/87ordered.t
t/92storage.t
t/92storage_ping_count.t [new file with mode: 0644]
t/93nobindvars.t
t/93storage_replication.t
t/95sql_maker_quote.t
t/99dbic_sqlt_parser.t
t/count/distinct.t
t/inflate/core.t
t/inflate/serialize.t
t/lib/DBICTest.pm
t/lib/DBICTest/Schema/CD.pm
t/lib/DBICTest/Schema/CustomSql.pm
t/lib/DBICTest/Schema/Serialized.pm
t/lib/sqlite.sql
t/multi_create/diamond.t [new file with mode: 0644]
t/multi_create/existing_in_chain.t [new file with mode: 0644]
t/multi_create/has_many.t [new file with mode: 0644]
t/multi_create/multilev_single_PKeqFK.t
t/multi_create/standard.t
t/prefetch/grouped.t

diff --git a/Changes b/Changes
index b195f04..36ae780 100644 (file)
--- a/Changes
+++ b/Changes
@@ -10,6 +10,8 @@ Revision history for DBIx::Class
           - Fixed the way we detect transaction to make this more reliable
             and forward looking.
           - Fixed some trouble with the way Moose Types are used.
+          - Made discard_chages/get_from_storage replication aware (they
+            now read from the master storage by default)
         - Refactor of MSSQL storage drivers, with some new features:
           - Support for placeholders for MSSQL via DBD::Sybase with proper
             autodetection
@@ -29,10 +31,15 @@ Revision history for DBIx::Class
             problems with search_related chaining
           - Deal with the distinct => 1 attribute properly when using
             prefetch
+        - An extension of the select-hashref syntax, allowing labeling
+          SQL-side aliasing: select => [ { max => 'foo', -as => 'bar' } ]
+        - Massive optimization of the DBI storage layer - reduce the
+          amount of connected() ping-calls
+        - Some fixes of multi-create corner cases
         - Multiple POD improvements
+        - Added exception when resultset is called without an argument
         - Improved support for non-schema-qualified tables under
-          Postgres.
-           - Fixed last_insert_id sequence detection.
+          Postgres (fixed last_insert_id sequence name auto-detection)
 
 0.08108 2009-07-05 23:15:00 (UTC)
         - Fixed the has_many prefetch with limit/group deficiency -
index 79da35f..0473058 100644 (file)
@@ -18,6 +18,7 @@ test_requires 'Test::Warn'          => 0.11;
 
 test_requires 'File::Temp'          => 0.22;
 
+
 # Core
 requires 'List::Util'               => 0;
 requires 'Scalar::Util'             => 0;
@@ -45,23 +46,17 @@ requires 'Sub::Name'                => 0.04;
 
 recommends 'SQL::Translator'        => 0.09004;
 
-install_script (qw|
-    script/dbicadmin
-|);
-
-tests_recursive (qw|
-    t
-|);
-
-resources 'IRC'         => 'irc://irc.perl.org/#dbix-class';
-resources 'license'     => 'http://dev.perl.org/licenses/';
-resources 'repository'  => 'http://dev.catalyst.perl.org/svnweb/bast/browse/DBIx-Class/';
-resources 'MailingList' => 'http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/dbix-class';
-
-
-# re-build README and require extra modules for testing if we're in a checkout
+my %replication_requires = (
+  'Moose',                    => 0.87,
+  'MooseX::AttributeHelpers'  => 0.21,
+  'MooseX::Types',            => 0.16,
+  'namespace::clean'          => 0.11,
+  'Hash::Merge',              => 0.11,
+);
 
 my %force_requires_if_author = (
+  %replication_requires,
+
 #  'Module::Install::Pod::Inherit' => 0.01,
   'Test::Pod::Coverage'       => 1.04,
   'SQL::Translator'           => 0.09007,
@@ -85,13 +80,6 @@ my %force_requires_if_author = (
   # t/96_is_deteministic_value.t
   'DateTime::Format::Strptime'=> 0,
 
-  # t/93storage_replication.t
-  'Moose',                    => 0.87,
-  'MooseX::AttributeHelpers'  => 0.21,
-  'MooseX::Types',            => 0.16,
-  'namespace::clean'          => 0.11,
-  'Hash::Merge',              => 0.11,
-
   # database-dependent reqs
   #
   $ENV{DBICTEST_PG_DSN}
@@ -115,6 +103,23 @@ my %force_requires_if_author = (
   ,
 );
 
+
+install_script (qw|
+    script/dbicadmin
+|);
+
+tests_recursive (qw|
+    t
+|);
+
+resources 'IRC'         => 'irc://irc.perl.org/#dbix-class';
+resources 'license'     => 'http://dev.perl.org/licenses/';
+resources 'repository'  => 'http://dev.catalyst.perl.org/svnweb/bast/browse/DBIx-Class/';
+resources 'MailingList' => 'http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/dbix-class';
+
+
+# re-build README and require extra modules for testing if we're in a checkout
+
 if ($Module::Install::AUTHOR) {
   warn <<'EOW';
 ******************************************************************************
@@ -139,8 +144,8 @@ EOW
     unlink 'MANIFEST';
   }
 
-#  eval { require Module::Install::Pod::Inherit };
-#  PodInherit() if !$@;
+#  require Module::Install::Pod::Inherit;
+#  PodInherit();
 }
 
 auto_install();
index 8728eb5..7561248 100644 (file)
@@ -103,8 +103,9 @@ Sometimes you have to run arbitrary SQL because your query is too complex
 be optimized for your database in a special way, but you still want to
 get the results as a L<DBIx::Class::ResultSet>.
 
-The recommended way to accomplish this is by defining a separate
-L<ResultSource::View|DBIx::Class::ResultSource::View> for your query.
+This is accomplished by defining a
+L<ResultSource::View|DBIx::Class::ResultSource::View> for your query,
+almost like you would define a regular ResultSource.
 
   package My::Schema::Result::UserFriendsComplex;
   use strict;
@@ -116,7 +117,9 @@ L<ResultSource::View|DBIx::Class::ResultSource::View> for your query.
 
   # ->table, ->add_columns, etc.
 
+  # do not attempt to deploy() this view
   __PACKAGE__->result_source_instance->is_virtual(1);
+
   __PACKAGE__->result_source_instance->view_definition(q[
     SELECT u.* FROM user u
     INNER JOIN user_friends f ON u.id = f.user_id
@@ -141,6 +144,21 @@ L</delete>, ...  on it).
 
 Note that you cannot have bind parameters unless is_virtual is set to true.
 
+=over
+
+=item * NOTE
+
+If you're using the old deprecated C<< $rsrc_instance->name(\'( SELECT ...') >>
+method for custom SQL execution, you are highly encouraged to update your code 
+to use a virtual view as above. If you do not want to change your code, and just
+want to suppress the deprecation warning when you call
+L<DBIx::Class::Schema/deploy>, add this line to your source definition, so that
+C<deploy> will exclude this "table":
+
+  sub sqlt_deploy_hook { $_[1]->schema->drop_table ($_[1]) }
+
+=back
+
 =head2 Using specific columns
 
 When you only want specific columns from a table, you can use
@@ -181,12 +199,33 @@ to access the returned value:
   # SELECT name name, LENGTH( name )
   # FROM artist
 
-Note that the C< as > attribute has absolutely nothing to with the sql
-syntax C< SELECT foo AS bar > (see the documentation in
-L<DBIx::Class::ResultSet/ATTRIBUTES>).  If your alias exists as a
-column in your base class (i.e. it was added with C<add_columns>), you
-just access it as normal. Our C<Artist> class has a C<name> column, so
-we just use the C<name> accessor:
+Note that the C<as> attribute B<has absolutely nothing to do> with the sql
+syntax C< SELECT foo AS bar > (see the documentation in 
+L<DBIx::Class::ResultSet/ATTRIBUTES>). You can control the C<AS> part of the
+generated SQL via the C<-as> field attribute as follows:
+
+  my $rs = $schema->resultset('Artist')->search(
+    {},
+    {
+      join => 'cds',
+      distinct => 1,
+      +select => [ { count => 'cds.cdid', -as => 'amount_of_cds' } ],
+      +as => [qw/num_cds/],
+      order_by => { -desc => 'amount_of_cds' },
+    }
+  );
+
+  # Equivalent SQL
+  # SELECT me.artistid, me.name, me.rank, me.charfield, COUNT( cds.cdid ) AS amount_of_cds 
+  #   FROM artist me LEFT JOIN cd cds ON cds.artist = me.artistid 
+  # GROUP BY me.artistid, me.name, me.rank, me.charfield 
+  # ORDER BY amount_of_cds DESC 
+
+
+If your alias exists as a column in your base class (i.e. it was added with
+L<add_columns|DBIx::Class::ResultSource/add_columns>), you just access it as
+normal. Our C<Artist> class has a C<name> column, so we just use the C<name>
+accessor:
 
   my $artist = $rs->first();
   my $name = $artist->name();
index dd37d0e..3a275e6 100644 (file)
@@ -200,8 +200,12 @@ many options.
 
 =head2 Connecting
 
-To connect to your Schema, you need to provide the connection details.  The
-arguments are the same as for L<DBI/connect>:
+To connect to your Schema, you need to provide the connection details or a
+database handle.
+
+=head3 Via connection details
+
+The arguments are the same as for L<DBI/connect>:
 
   my $schema = My::Schema->connect('dbi:SQLite:/home/me/myapp/my.db');
 
@@ -227,6 +231,16 @@ a special fifth argument to connect:
 See L<DBIx::Class::Schema::Storage::DBI/connect_info> for more information about
 this and other special C<connect>-time options.
 
+=head3 Via a database handle
+
+The supplied coderef is expected to return a single connected database handle
+(e.g. a L<DBI> C<$dbh>)
+
+  my $schema = My::Schema->connect (
+    sub { Some::DBH::Factory->connect },
+    \%extra_attrs,
+  );
+
 =head2 Basic usage
 
 Once you've defined the basic classes, either manually or using
index 9f2e253..59162bb 100644 (file)
@@ -362,38 +362,58 @@ sub move_to {
     my( $self, $to_position ) = @_;
     return 0 if ( $to_position < 1 );
 
-    my $from_position = $self->_position;
-    return 0 if ( $from_position == $to_position );
-
     my $position_column = $self->position_column;
 
-    {
-        my $guard = $self->result_source->schema->txn_scope_guard;
+    my $guard;
 
-        my ($direction, @between);
-        if ( $from_position < $to_position ) {
-            $direction = -1;
-            @between = map { $self->_position_value ($_) } ( $from_position + 1, $to_position );
-        }
-        else {
-            $direction = 1;
-            @between = map { $self->_position_value ($_) } ( $to_position, $from_position - 1 );
-        }
+    if ($self->is_column_changed ($position_column) ) {
+      # something changed our position, we have no idea where we
+      # used to be - requery without using discard_changes
+      # (we need only a specific column back)
 
-        my $new_pos_val = $self->_position_value ($to_position);                              # record this before the shift
+      $guard = $self->result_source->schema->txn_scope_guard;
 
-        # we need to null-position the moved row if the position column is part of a constraint
-        if (grep { $_ eq $position_column } ( map { @$_ } (values %{{ $self->result_source->unique_constraints }} ) ) ) {
-            $self->_ordered_internal_update({ $position_column => $self->null_position_value });
-        }
+      my $cursor = $self->result_source->resultset->search(
+        $self->ident_condition,
+        { select => $position_column },
+      )->cursor;
 
-        $self->_shift_siblings ($direction, @between);
-        $self->_ordered_internal_update({ $position_column => $new_pos_val });
+      my ($pos) = $cursor->next;
+      $self->$position_column ($pos);
+      delete $self->{_dirty_columns}{$position_column};
+    }
 
-        $guard->commit;
+    my $from_position = $self->_position;
 
-        return 1;
+    if ( $from_position == $to_position ) {   # FIXME this will not work for non-numeric order
+      $guard->commit if $guard;
+      return 0;
     }
+
+    $guard ||= $self->result_source->schema->txn_scope_guard;
+
+    my ($direction, @between);
+    if ( $from_position < $to_position ) {
+      $direction = -1;
+      @between = map { $self->_position_value ($_) } ( $from_position + 1, $to_position );
+    }
+    else {
+      $direction = 1;
+      @between = map { $self->_position_value ($_) } ( $to_position, $from_position - 1 );
+    }
+
+    my $new_pos_val = $self->_position_value ($to_position);  # record this before the shift
+
+    # we need to null-position the moved row if the position column is part of a constraint
+    if (grep { $_ eq $position_column } ( map { @$_ } (values %{{ $self->result_source->unique_constraints }} ) ) ) {
+      $self->_ordered_internal_update({ $position_column => $self->null_position_value });
+    }
+
+    $self->_shift_siblings ($direction, @between);
+    $self->_ordered_internal_update({ $position_column => $new_pos_val });
+
+    $guard->commit;
+    return 1;
 }
 
 =head2 move_to_group
@@ -428,43 +448,72 @@ sub move_to_group {
     my $position_column = $self->position_column;
 
     return 0 if ( defined($to_position) and $to_position < 1 );
-    if ($self->_is_in_group ($to_group) ) {
-        return 0 if not defined $to_position;
-        return $self->move_to ($to_position);
+
+    # check if someone changed the _grouping_columns - this will
+    # prevent _is_in_group working, so we need to requery the db
+    # for the original values
+    my (@dirty_cols, %values, $guard);
+    for ($self->_grouping_columns) {
+      $values{$_} = $self->get_column ($_);
+      push @dirty_cols, $_ if $self->is_column_changed ($_);
     }
 
-    {
-        my $guard = $self->result_source->schema->txn_scope_guard;
+    # re-query only the dirty columns, and restore them on the
+    # object (subsequent code will update them to the correct
+    # after-move values)
+    if (@dirty_cols) {
+      $guard = $self->result_source->schema->txn_scope_guard;
 
-        # Move to end of current group to adjust siblings
-        $self->move_last;
+      my $cursor = $self->result_source->resultset->search(
+        $self->ident_condition,
+        { select => \@dirty_cols },
+      )->cursor;
 
-        $self->set_inflated_columns({ %$to_group, $position_column => undef });
-        my $new_group_last_posval = $self->_last_sibling_posval;
-        my $new_group_last_position = $self->_position_from_value (
-          $new_group_last_posval
-        );
+      my @original_values = $cursor->next;
+      $self->set_inflated_columns ({ %values, map { $_ => shift @original_values } (@dirty_cols) });
+      delete $self->{_dirty_columns}{$_} for (@dirty_cols);
+    }
 
-        if ( not defined($to_position) or $to_position > $new_group_last_position) {
-            $self->set_column(
-                $position_column => $new_group_last_position
-                    ? $self->_next_position_value ( $new_group_last_posval )
-                    : $self->_initial_position_value
-            );
-        }
-        else {
-            my $bumped_pos_val = $self->_position_value ($to_position);
-            my @between = ($to_position, $new_group_last_position);
-            $self->_shift_siblings (1, @between);   #shift right
-            $self->set_column( $position_column => $bumped_pos_val );
-        }
+    if ($self->_is_in_group ($to_group) ) {
+      my $ret;
+      if (defined $to_position) {
+        $ret = $self->move_to ($to_position);
+      }
+
+      $guard->commit if $guard;
+      return $ret||0;
+    }
 
-        $self->_ordered_internal_update;
+    $guard ||= $self->result_source->schema->txn_scope_guard;
 
-        $guard->commit;
+    # Move to end of current group to adjust siblings
+    $self->move_last;
 
-        return 1;
+    $self->set_inflated_columns({ %$to_group, $position_column => undef });
+    my $new_group_last_posval = $self->_last_sibling_posval;
+    my $new_group_last_position = $self->_position_from_value (
+      $new_group_last_posval
+    );
+
+    if ( not defined($to_position) or $to_position > $new_group_last_position) {
+      $self->set_column(
+        $position_column => $new_group_last_position
+          ? $self->_next_position_value ( $new_group_last_posval )
+          : $self->_initial_position_value
+      );
+    }
+    else {
+      my $bumped_pos_val = $self->_position_value ($to_position);
+      my @between = ($to_position, $new_group_last_position);
+      $self->_shift_siblings (1, @between);   #shift right
+      $self->set_column( $position_column => $bumped_pos_val );
     }
+
+    $self->_ordered_internal_update;
+
+    $guard->commit;
+
+    return 1;
 }
 
 =head2 insert
@@ -508,16 +557,47 @@ sub update {
     # this is set by _ordered_internal_update()
     return $self->next::method(@_) if $self->{_ORDERED_INTERNAL_UPDATE};
 
-    my $upd = shift;
-    $self->set_inflated_columns($upd) if $upd;
-    my %changes = $self->get_dirty_columns;
-    $self->discard_changes;
-
     my $position_column = $self->position_column;
+    my @ordering_columns = ($self->_grouping_columns, $position_column);
+
+
+    # these steps are necessary to keep the external appearance of
+    # ->update($upd) so that other things overloading update() will
+    # work properly
+    my %original_values = $self->get_columns;
+    my %existing_changes = $self->get_dirty_columns;
+
+    # See if any of the *supplied* changes would affect the ordering
+    # The reason this is so contrived, is that we want to leverage
+    # the datatype aware value comparing, while at the same time
+    # keep the original value intact (it will be updated later by the
+    # corresponding routine)
+
+    my %upd = %{shift || {}};
+    my %changes = %existing_changes;
+
+    for (@ordering_columns) {
+        next unless exists $upd{$_};
+
+        # we do not want to keep propagating this to next::method
+        # as it will be a done deal by the time get there
+        my $value = delete $upd{$_};
+        $self->set_inflated_columns ({ $_ => $value });
+
+        # see if an update resulted in a dirty column
+        # it is important to preserve the old value, as it
+        # will be needed to carry on a successfull move()
+        # operation without re-querying the database
+        if ($self->is_column_changed ($_) && not exists $existing_changes{$_}) {
+            $changes{$_} = $value;
+            $self->set_inflated_columns ({ $_ => $original_values{$_} });
+            delete $self->{_dirty_columns}{$_};
+        }
+    }
 
     # if nothing group/position related changed - short circuit
-    if (not grep { exists $changes{$_} } ($self->_grouping_columns, $position_column) ) {
-        return $self->next::method( \%changes, @_ );
+    if (not grep { exists $changes{$_} } ( @ordering_columns ) ) {
+        return $self->next::method( \%upd, @_ );
     }
 
     {
@@ -529,37 +609,37 @@ sub update {
             # create new_group by taking the current group and inserting changes
             my $new_group = {$self->_grouping_clause};
             foreach my $col (keys %$new_group) {
-                if (exists $changes{$col}) {
-                    $new_group->{$col} = delete $changes{$col}; # don't want to pass this on to next::method
-                }
+                $new_group->{$col} = $changes{$col} if exists $changes{$col};
             }
 
             $self->move_to_group(
                 $new_group,
                 (exists $changes{$position_column}
-                    # The FIXME bit contradicts the documentation: when changing groups without supplying explicit
-                    # positions in move_to_group(), we push the item to the end of the group.
-                    # However when I was rewriting this, the position from the old group was clearly passed to the new one
+                    # The FIXME bit contradicts the documentation: POD states that
+                    # when changing groups without supplying explicit positions in
+                    # move_to_group(), we push the item to the end of the group.
+                    # However when I was rewriting this, the position from the old
+                    # group was clearly passed to the new one
                     # Probably needs to go away (by ribasushi)
-                    ? delete $changes{$position_column}     # means there was a position change supplied with the update too
-                    : $self->_position                      # FIXME!
+                    ? $changes{$position_column}    # means there was a position change supplied with the update too
+                    : $self->_position              # FIXME! (replace with undef)
                 ),
             );
         }
         elsif (exists $changes{$position_column}) {
-            $self->move_to(delete $changes{$position_column});
+            $self->move_to($changes{$position_column});
         }
 
         my @res;
         my $want = wantarray();
         if (not defined $want) {
-            $self->next::method( \%changes, @_ );
+            $self->next::method( \%upd, @_ );
         }
         elsif ($want) {
-            @res = $self->next::method( \%changes, @_ );
+            @res = $self->next::method( \%upd, @_ );
         }
         else {
-            $res[0] = $self->next::method( \%changes, @_ );
+            $res[0] = $self->next::method( \%upd, @_ );
         }
 
         $guard->commit;
@@ -790,8 +870,8 @@ sub _siblings {
 =head2 _grouping_clause
 
 This method returns one or more name=>value pairs for limiting a search
-by the grouping column(s).  If the grouping column is not
-defined then this will return an empty list.
+by the grouping column(s).  If the grouping column is not defined then 
+this will return an empty list.
 
 =cut
 sub _grouping_clause {
index 5be4833..cf8a194 100644 (file)
@@ -20,45 +20,6 @@ depending on them.
 
 =cut
 
-sub _ident_values {
-  my ($self) = @_;
-  return (map { $self->{_column_data}{$_} } $self->primary_columns);
-}
-
-=head2 discard_changes ($attrs)
-
-Re-selects the row from the database, losing any changes that had
-been made.
-
-This method can also be used to refresh from storage, retrieving any
-changes made since the row was last read from storage.
-
-$attrs is expected to be a hashref of attributes suitable for passing as the
-second argument to $resultset->search($cond, $attrs);
-
-=cut
-
-sub discard_changes {
-  my ($self, $attrs) = @_;
-  delete $self->{_dirty_columns};
-  return unless $self->in_storage; # Don't reload if we aren't real!
-
-  if( my $current_storage = $self->get_from_storage($attrs)) {
-
-    # Set $self to the current.
-       %$self = %$current_storage;
-
-    # Avoid a possible infinite loop with
-    # sub DESTROY { $_[0]->discard_changes }
-    bless $current_storage, 'Do::Not::Exist';
-
-    return $self;      
-  } else {
-    $self->in_storage(0);
-    return $self;      
-  }
-}
-
 =head2 id
 
 Returns the primary key(s) for a row. Can't be called as
@@ -74,12 +35,28 @@ sub id {
   return (wantarray ? @pk : $pk[0]);
 }
 
+sub _ident_values {
+  my ($self) = @_;
+  return (map { $self->{_column_data}{$_} } $self->primary_columns);
+}
+
 =head2 ID
 
 Returns a unique id string identifying a row object by primary key.
 Used by L<DBIx::Class::CDBICompat::LiveObjectIndex> and
 L<DBIx::Class::ObjectCache>.
 
+=over
+
+=item WARNING
+
+The default C<_create_ID> method used by this function orders the returned
+values by the alphabetical order of the primary column names, B<unlike>
+the L</id> method, which follows the same order in which columns were fed
+to L<DBIx::Class::ResultSource/set_primary_key>.
+
+=back
+
 =cut
 
 sub ID {
index e24dafe..7fa5aa1 100644 (file)
@@ -1266,8 +1266,8 @@ sub _count_subq_rs {
   # extra selectors do not go in the subquery and there is no point of ordering it
   delete $sub_attrs->{$_} for qw/collapse select _prefetch_select as order_by/;
 
-  # if we prefetch, we group_by primary keys only as this is what we would get out of the rs via ->next/->all
-  # clobber old group_by regardless
+  # if we prefetch, we group_by primary keys only as this is what we would get out
+  # of the rs via ->next/->all. We DO WANT to clobber old group_by regardless
   if ( keys %{$attrs->{collapse}} ) {
     $sub_attrs->{group_by} = [ map { "$attrs->{alias}.$_" } ($rsrc->primary_columns) ]
   }
@@ -1509,7 +1509,8 @@ sub _rs_update_delete {
       if (my $g = $attrs->{group_by}) {
         my @current_group_by = map
           { $_ =~ /\./ ? $_ : "$attrs->{alias}.$_" }
-          (ref $g eq 'ARRAY' ? @$g : $g );
+          @$g
+        ;
 
         if (
           join ("\x00", sort @current_group_by)
@@ -2871,7 +2872,7 @@ sub _resolved_attrs {
     );
   }
 
-  if ($attrs->{group_by} and ! ref $attrs->{group_by}) {
+  if ($attrs->{group_by} and ref $attrs->{group_by} ne 'ARRAY') {
     $attrs->{group_by} = [ $attrs->{group_by} ];
   }
 
index 1b93f4e..dfa4c78 100644 (file)
@@ -584,7 +584,10 @@ optional constraint name.
 sub name_unique_constraint {
   my ($self, $cols) = @_;
 
-  return join '_', $self->name, @$cols;
+  my $name = $self->name;
+  $name = $$name if (ref $name eq 'SCALAR');
+
+  return join '_', $name, @$cols;
 }
 
 =head2 unique_constraints
@@ -1262,18 +1265,22 @@ sub pk_depends_on {
 # hashref of columns of the related object.
 sub _pk_depends_on {
   my ($self, $relname, $rel_data) = @_;
-  my $cond = $self->relationship_info($relname)->{cond};
 
+  my $relinfo = $self->relationship_info($relname);
+
+  # don't assume things if the relationship direction is specified
+  return $relinfo->{attrs}{is_foreign_key_constraint}
+    if exists ($relinfo->{attrs}{is_foreign_key_constraint});
+
+  my $cond = $relinfo->{cond};
   return 0 unless ref($cond) eq 'HASH';
 
   # map { foreign.foo => 'self.bar' } to { bar => 'foo' }
-
   my $keyhash = { map { my $x = $_; $x =~ s/.*\.//; $x; } reverse %$cond };
 
   # assume anything that references our PK probably is dependent on us
   # rather than vice versa, unless the far side is (a) defined or (b)
   # auto-increment
-
   my $rel_source = $self->related_source($relname);
 
   foreach my $p ($self->primary_columns) {
index 929c300..db82b47 100644 (file)
@@ -5,6 +5,9 @@ use warnings;
 
 use base qw/DBIx::Class::ResultSourceProxy/;
 
+use DBIx::Class::ResultSource::Table;
+use Scalar::Util ();
+
 __PACKAGE__->mk_classdata(table_class => 'DBIx::Class::ResultSource::Table');
 
 __PACKAGE__->mk_classdata('table_alias'); # FIXME: Doesn't actually do
@@ -76,7 +79,8 @@ Gets or sets the table name.
 sub table {
   my ($class, $table) = @_;
   return $class->result_source_instance->name unless $table;
-  unless (ref $table) {
+
+  unless (Scalar::Util::blessed($table) && $table->isa($class->table_class)) {
 
     my $table_class = $class->table_class;
     $class->ensure_class_loaded($table_class);
index 805c50b..5d6285f 100644 (file)
@@ -164,8 +164,6 @@ sub new {
       unless ref($attrs) eq 'HASH';
 
     my ($related,$inflated);
-    ## Pretend all the rels are actual objects, unset below if not, for insert() to fix
-    $new->{_rel_in_storage} = 1;
 
     foreach my $key (keys %$attrs) {
       if (ref $attrs->{$key}) {
@@ -181,9 +179,9 @@ sub new {
           }
 
           if ($rel_obj->in_storage) {
+            $new->{_rel_in_storage}{$key} = 1;
             $new->set_from_related($key, $rel_obj);
           } else {
-            $new->{_rel_in_storage} = 0;
             MULTICREATE_DEBUG and warn "MC $new uninserted $key $rel_obj\n";
           }
 
@@ -202,13 +200,11 @@ sub new {
             }
 
             if ($rel_obj->in_storage) {
-              $new->set_from_related($key, $rel_obj);
+              $rel_obj->throw_exception ('A multi relationship can not be pre-existing when doing multicreate. Something went wrong');
             } else {
-              $new->{_rel_in_storage} = 0;
               MULTICREATE_DEBUG and
                 warn "MC $new uninserted $key $rel_obj (${\($idx+1)} of $total)\n";
             }
-            $new->set_from_related($key, $rel_obj) if $rel_obj->in_storage;
             push(@objects, $rel_obj);
           }
           $related->{$key} = \@objects;
@@ -221,8 +217,10 @@ sub new {
           if(!Scalar::Util::blessed($rel_obj)) {
             $rel_obj = $new->__new_related_find_or_new_helper($key, $rel_obj);
           }
-          unless ($rel_obj->in_storage) {
-            $new->{_rel_in_storage} = 0;
+          if ($rel_obj->in_storage) {
+            $new->{_rel_in_storage}{$key} = 1;
+          }
+          else {
             MULTICREATE_DEBUG and warn "MC $new uninserted $key $rel_obj";
           }
           $inflated->{$key} = $rel_obj;
@@ -286,27 +284,21 @@ sub insert {
   my %related_stuff = (%{$self->{_relationship_data} || {}},
                        %{$self->{_inflated_column} || {}});
 
-  if(!$self->{_rel_in_storage}) {
-
-    # The guard will save us if we blow out of this scope via die
-    $rollback_guard = $source->storage->txn_scope_guard;
-
-    ## Should all be in relationship_data, but we need to get rid of the
-    ## 'filter' reltype..
-    ## These are the FK rels, need their IDs for the insert.
+  # insert what needs to be inserted before us
+  my %pre_insert;
+  for my $relname (keys %related_stuff) {
+    my $rel_obj = $related_stuff{$relname};
 
-    my @pri = $self->primary_columns;
+    if (! $self->{_rel_in_storage}{$relname}) {
+      next unless (Scalar::Util::blessed($rel_obj)
+                    && $rel_obj->isa('DBIx::Class::Row'));
 
-    REL: foreach my $relname (keys %related_stuff) {
+      next unless $source->_pk_depends_on(
+                    $relname, { $rel_obj->get_columns }
+                  );
 
-      my $rel_obj = $related_stuff{$relname};
-
-      next REL unless (Scalar::Util::blessed($rel_obj)
-                       && $rel_obj->isa('DBIx::Class::Row'));
-
-      next REL unless $source->_pk_depends_on(
-                        $relname, { $rel_obj->get_columns }
-                      );
+      # The guard will save us if we blow out of this scope via die
+      $rollback_guard ||= $source->storage->txn_scope_guard;
 
       MULTICREATE_DEBUG and warn "MC $self pre-reconstructing $relname $rel_obj\n";
 
@@ -315,10 +307,19 @@ sub insert {
                     ->related_source($relname)
                     ->resultset
                     ->find_or_create($them);
+
       %{$rel_obj} = %{$re};
-      $self->set_from_related($relname, $rel_obj);
-      delete $related_stuff{$relname};
+      $self->{_rel_in_storage}{$relname} = 1;
     }
+
+    $self->set_from_related($relname, $rel_obj);
+    delete $related_stuff{$relname};
+  }
+
+  # start a transaction here if not started yet and there is more stuff
+  # to insert after us
+  if (keys %related_stuff) {
+    $rollback_guard ||= $source->storage->txn_scope_guard
   }
 
   MULTICREATE_DEBUG and do {
@@ -332,13 +333,12 @@ sub insert {
 
   ## PK::Auto
   my @auto_pri = grep {
-                   !defined $self->get_column($_) ||
-                   ref($self->get_column($_)) eq 'SCALAR'
+                  (not defined $self->get_column($_))
+                    ||
+                  (ref($self->get_column($_)) eq 'SCALAR')
                  } $self->primary_columns;
 
   if (@auto_pri) {
-    #$self->throw_exception( "More than one possible key found for auto-inc on ".ref $self )
-    #  if defined $too_many;
     MULTICREATE_DEBUG and warn "MC $self fetching missing PKs ".join(', ', @auto_pri)."\n";
     my $storage = $self->result_source->storage;
     $self->throw_exception( "Missing primary key but Storage doesn't support last_insert_id" )
@@ -353,47 +353,47 @@ sub insert {
   $self->{_dirty_columns} = {};
   $self->{related_resultsets} = {};
 
-  if(!$self->{_rel_in_storage}) {
-    ## Now do the relationships that need our ID (has_many etc.)
-    foreach my $relname (keys %related_stuff) {
-      my $rel_obj = $related_stuff{$relname};
-      my @cands;
-      if (Scalar::Util::blessed($rel_obj)
-          && $rel_obj->isa('DBIx::Class::Row')) {
-        @cands = ($rel_obj);
-      } elsif (ref $rel_obj eq 'ARRAY') {
-        @cands = @$rel_obj;
-      }
-      if (@cands) {
-        my $reverse = $source->reverse_relationship_info($relname);
-        foreach my $obj (@cands) {
-          $obj->set_from_related($_, $self) for keys %$reverse;
-          my $them = { %{$obj->{_relationship_data} || {} }, $obj->get_inflated_columns };
-          if ($self->__their_pk_needs_us($relname, $them)) {
-            if (exists $self->{_ignore_at_insert}{$relname}) {
-              MULTICREATE_DEBUG and warn "MC $self skipping post-insert on $relname";
-            } else {
-              MULTICREATE_DEBUG and warn "MC $self re-creating $relname $obj";
-              my $re = $self->result_source
-                            ->related_source($relname)
-                            ->resultset
-                            ->find_or_create($them);
-              %{$obj} = %{$re};
-              MULTICREATE_DEBUG and warn "MC $self new $relname $obj";
-            }
+  foreach my $relname (keys %related_stuff) {
+    next unless $source->has_relationship ($relname);
+
+    my @cands = ref $related_stuff{$relname} eq 'ARRAY'
+      ? @{$related_stuff{$relname}}
+      : $related_stuff{$relname}
+    ;
+
+    if (@cands
+          && Scalar::Util::blessed($cands[0])
+            && $cands[0]->isa('DBIx::Class::Row')
+    ) {
+      my $reverse = $source->reverse_relationship_info($relname);
+      foreach my $obj (@cands) {
+        $obj->set_from_related($_, $self) for keys %$reverse;
+        my $them = { %{$obj->{_relationship_data} || {} }, $obj->get_inflated_columns };
+        if ($self->__their_pk_needs_us($relname, $them)) {
+          if (exists $self->{_ignore_at_insert}{$relname}) {
+            MULTICREATE_DEBUG and warn "MC $self skipping post-insert on $relname";
           } else {
-            MULTICREATE_DEBUG and warn "MC $self post-inserting $obj";
-            $obj->insert();
+            MULTICREATE_DEBUG and warn "MC $self re-creating $relname $obj";
+            my $re = $self->result_source
+                          ->related_source($relname)
+                          ->resultset
+                          ->create($them);
+            %{$obj} = %{$re};
+            MULTICREATE_DEBUG and warn "MC $self new $relname $obj";
           }
+        } else {
+          MULTICREATE_DEBUG and warn "MC $self post-inserting $obj";
+          $obj->insert();
         }
       }
     }
-    delete $self->{_ignore_at_insert};
-    $rollback_guard->commit;
   }
 
   $self->in_storage(1);
-  undef $self->{_orig_ident};
+  delete $self->{_orig_ident};
+  delete $self->{_ignore_at_insert};
+  $rollback_guard->commit if $rollback_guard;
+
   return $self;
 }
 
@@ -1277,12 +1277,51 @@ sub get_from_storage {
     my $resultset = $self->result_source->resultset;
 
     if(defined $attrs) {
-       $resultset = $resultset->search(undef, $attrs);
+      $resultset = $resultset->search(undef, $attrs);
     }
 
     return $resultset->find($self->{_orig_ident} || $self->ident_condition);
 }
 
+=head2 discard_changes ($attrs)
+
+Re-selects the row from the database, losing any changes that had
+been made.
+
+This method can also be used to refresh from storage, retrieving any
+changes made since the row was last read from storage.
+
+$attrs is expected to be a hashref of attributes suitable for passing as the
+second argument to $resultset->search($cond, $attrs);
+
+=cut
+
+sub discard_changes {
+  my ($self, $attrs) = @_;
+  delete $self->{_dirty_columns};
+  return unless $self->in_storage; # Don't reload if we aren't real!
+
+  # add a replication default to read from the master only
+  $attrs = { force_pool => 'master', %{$attrs||{}} };
+
+  if( my $current_storage = $self->get_from_storage($attrs)) {
+
+    # Set $self to the current.
+    %$self = %$current_storage;
+
+    # Avoid a possible infinite loop with
+    # sub DESTROY { $_[0]->discard_changes }
+    bless $current_storage, 'Do::Not::Exist';
+
+    return $self;
+  }
+  else {
+    $self->in_storage(0);
+    return $self;
+  }
+}
+
+
 =head2 throw_exception
 
 See L<DBIx::Class::Schema/throw_exception>.
index c03adbb..d5041ba 100644 (file)
@@ -329,12 +329,10 @@ sub select {
 
   $self->{"${_}_bind"} = [] for (qw/having from order/);
 
-  if (ref $table eq 'SCALAR') {
-    $table = $$table;
-  }
-  elsif (not ref $table) {
+  if (not ref($table) or ref($table) eq 'SCALAR') {
     $table = $self->_quote($table);
   }
+
   local $self->{rownum_hack_count} = 1
     if (defined $rest[0] && $self->{limit_dialect} eq 'RowNum');
   @rest = (-1) unless defined $rest[0];
@@ -354,7 +352,7 @@ sub select {
 sub insert {
   my $self = shift;
   my $table = shift;
-  $table = $self->_quote($table) unless ref($table);
+  $table = $self->_quote($table);
 
   # SQLA will emit INSERT INTO $table ( ) VALUES ( )
   # which is sadly understood only by MySQL. Change default behavior here,
@@ -370,7 +368,7 @@ sub insert {
 sub update {
   my $self = shift;
   my $table = shift;
-  $table = $self->_quote($table) unless ref($table);
+  $table = $self->_quote($table);
   $self->SUPER::update($table, @_);
 }
 
@@ -378,7 +376,7 @@ sub update {
 sub delete {
   my $self = shift;
   my $table = shift;
-  $table = $self->_quote($table) unless ref($table);
+  $table = $self->_quote($table);
   $self->SUPER::delete($table, @_);
 }
 
@@ -407,35 +405,33 @@ sub _recurse_fields {
   }
   elsif ($ref eq 'HASH') {
     my %hash = %$fields;
-    my ($select, $as);
 
-    if ($hash{-select}) {
-      $select = $self->_recurse_fields (delete $hash{-select});
-      $as = $self->_quote (delete $hash{-as});
-    }
-    else {
-      my ($func, $args) = each %hash;
-      delete $hash{$func};
-
-      if (lc ($func) eq 'distinct' && ref $args eq 'ARRAY' && @$args > 1) {
-        croak (
-          'The select => { distinct => ... } syntax is not supported for multiple columns.'
-         .' Instead please use { group_by => [ qw/' . (join ' ', @$args) . '/ ] }'
-         .' or { select => [ qw/' . (join ' ', @$args) . '/ ], distinct => 1 }'
-        );
-      }
-      $select = sprintf ('%s( %s )',
-        $self->_sqlcase($func),
-        $self->_recurse_fields($args)
+    my $as = delete $hash{-as};   # if supplied
+
+    my ($func, $args) = each %hash;
+    delete $hash{$func};
+
+    if (lc ($func) eq 'distinct' && ref $args eq 'ARRAY' && @$args > 1) {
+      croak (
+        'The select => { distinct => ... } syntax is not supported for multiple columns.'
+       .' Instead please use { group_by => [ qw/' . (join ' ', @$args) . '/ ] }'
+       .' or { select => [ qw/' . (join ' ', @$args) . '/ ], distinct => 1 }'
       );
     }
 
+    my $select = sprintf ('%s( %s )%s',
+      $self->_sqlcase($func),
+      $self->_recurse_fields($args),
+      $as
+        ? sprintf (' %s %s', $self->_sqlcase('as'), $as)
+        : ''
+    );
+
     # there should be nothing left
     if (keys %hash) {
       croak "Malformed select argument - too many keys in hash: " . join (',', keys %$fields );
     }
 
-    $select .= " AS $as" if $as;
     return $select;
   }
   # Is the second check absolutely necessary?
@@ -584,6 +580,7 @@ sub _join_condition {
 sub _quote {
   my ($self, $label) = @_;
   return '' unless defined $label;
+  return $$label if ref($label) eq 'SCALAR';
   return "*" if $label eq '*';
   return $label unless $self->{quote_char};
   if(ref $self->{quote_char} eq "ARRAY"){
index 9b4d0be..687a793 100644 (file)
@@ -12,7 +12,7 @@ sub insert {
   my $self = shift;
 
   my $table = $_[0];
-  $table = $self->_quote($table) unless ref($table);
+  $table = $self->_quote($table);
 
   if (! $_[1] or (ref $_[1] eq 'HASH' and !keys %{$_[1]} ) ) {
     return "INSERT INTO ${table} () VALUES ()"
index b37ec5e..a7080e2 100644 (file)
@@ -42,7 +42,7 @@ DBIx::Class::Schema - composable schemas
     $dsn,
     $user,
     $password,
-    { AutoCommit => 0 },
+    { AutoCommit => 1 },
   );
 
   my $schema2 = Library::Schema->connect($coderef_returning_dbh);
@@ -543,6 +543,8 @@ name.
 
 sub resultset {
   my ($self, $moniker) = @_;
+  $self->throw_exception('resultset() expects a source name')
+    unless defined $moniker;
   return $self->source($moniker)->resultset;
 }
 
index 32be00f..b8f3ebb 100644 (file)
@@ -437,12 +437,28 @@ sub connect_info {
     }
   }
 
-  %attrs = () if (ref $args[0] eq 'CODE');  # _connect() never looks past $args[0] in this case
+  if (ref $args[0] eq 'CODE') {
+    # _connect() never looks past $args[0] in this case
+    %attrs = ()
+  } else {
+    %attrs = (
+      %{ $self->_default_dbi_connect_attributes || {} },
+      %attrs,
+    );
+  }
 
   $self->_dbi_connect_info([@args, keys %attrs ? \%attrs : ()]);
   $self->_connect_info;
 }
 
+sub _default_dbi_connect_attributes {
+  return {
+    AutoCommit => 1,
+    RaiseError => 1,
+    PrintError => 0,
+  };
+}
+
 =head2 on_connect_do
 
 This method is deprecated in favour of setting via L</connect_info>.
@@ -539,6 +555,7 @@ sub dbh_do {
     }
   };
 
+  # ->connected might unset $@ - copy
   my $exception = $@;
   if(!$exception) { return $want_array ? @result : $result[0] }
 
@@ -546,6 +563,8 @@ sub dbh_do {
 
   # We were not connected - reconnect and retry, but let any
   #  exception fall right through this time
+  carp "Retrying $code after catching disconnected exception: $exception"
+    if $ENV{DBIC_DBIRETRY_DEBUG};
   $self->_populate_dbh;
   $self->$code($self->_dbh, @_);
 }
@@ -586,10 +605,11 @@ sub txn_do {
       $self->txn_commit;
     };
 
+    # ->connected might unset $@ - copy
     my $exception = $@;
     if(!$exception) { return $want_array ? @result : $result[0] }
 
-    if($tried++ > 0 || $self->connected) {
+    if($tried++ || $self->connected) {
       eval { $self->txn_rollback };
       my $rollback_exception = $@;
       if($rollback_exception) {
@@ -607,6 +627,8 @@ sub txn_do {
 
     # We were not connected, and was first try - reconnect and retry
     # via the while loop
+    carp "Retrying $coderef after catching disconnected exception: $exception"
+      if $ENV{DBIC_DBIRETRY_DEBUG};
     $self->_populate_dbh;
   }
 }
@@ -621,7 +643,7 @@ database is not in C<AutoCommit> mode.
 sub disconnect {
   my ($self) = @_;
 
-  if( $self->connected ) {
+  if( $self->_dbh ) {
     my @actions;
 
     push @actions, ( $self->on_disconnect_call || () );
@@ -658,23 +680,49 @@ sub with_deferred_fk_checks {
   $sub->();
 }
 
+=head2 connected
+
+=over
+
+=item Arguments: none
+
+=item Return Value: 1|0
+
+=back
+
+Verifies that the the current database handle is active and ready to execute
+an SQL statement (i.e. the connection did not get stale, server is still
+answering, etc.) This method is used internally by L</dbh>.
+
+=cut
+
 sub connected {
-  my ($self) = @_;
+  my $self = shift;
+  return 0 unless $self->_seems_connected;
 
-  if(my $dbh = $self->_dbh) {
-      if(defined $self->_conn_tid && $self->_conn_tid != threads->tid) {
-          $self->_dbh(undef);
-          $self->{_dbh_gen}++;
-          return;
-      }
-      else {
-          $self->_verify_pid;
-          return 0 if !$self->_dbh;
-      }
-      return ($dbh->FETCH('Active') && $self->_ping);
+  #be on the safe side
+  local $self->_dbh->{RaiseError} = 1;
+
+  return $self->_ping;
+}
+
+sub _seems_connected {
+  my $self = shift;
+
+  my $dbh = $self->_dbh
+    or return 0;
+
+  if(defined $self->_conn_tid && $self->_conn_tid != threads->tid) {
+    $self->_dbh(undef);
+    $self->{_dbh_gen}++;
+    return 0;
+  }
+  else {
+    $self->_verify_pid;
+    return 0 if !$self->_dbh;
   }
 
-  return 0;
+  return $dbh->FETCH('Active');
 }
 
 sub _ping {
@@ -709,21 +757,41 @@ sub ensure_connected {
 
 =head2 dbh
 
-Returns the dbh - a data base handle of class L<DBI>.
+Returns a C<$dbh> - a data base handle of class L<DBI>. The returned handle
+is guaranteed to be healthy by implicitly calling L</connected>, and if
+necessary performing a reconnection before returning. Keep in mind that this
+is very B<expensive> on some database engines. Consider using L<dbh_do>
+instead.
 
 =cut
 
 sub dbh {
   my ($self) = @_;
 
-  $self->ensure_connected;
+  if (not $self->_dbh) {
+    $self->_populate_dbh;
+  } else {
+    $self->ensure_connected;
+  }
+  return $self->_dbh;
+}
+
+# this is the internal "get dbh or connect (don't check)" method
+sub _get_dbh {
+  my $self = shift;
+  $self->_populate_dbh unless $self->_dbh;
   return $self->_dbh;
 }
 
 sub _sql_maker_args {
     my ($self) = @_;
 
-    return ( bindtype=>'columns', array_datatypes => 1, limit_dialect => $self->dbh, %{$self->_sql_maker_opts} );
+    return (
+      bindtype=>'columns',
+      array_datatypes => 1,
+      limit_dialect => $self->_get_dbh,
+      %{$self->_sql_maker_opts}
+    );
 }
 
 sub sql_maker {
@@ -740,7 +808,9 @@ sub _rebless {}
 
 sub _populate_dbh {
   my ($self) = @_;
+
   my @info = @{$self->_dbi_connect_info || []};
+  $self->_dbh(undef); # in case ->connected failed we might get sent here
   $self->_dbh($self->_connect(@info));
 
   $self->_conn_pid($$);
@@ -752,6 +822,11 @@ sub _populate_dbh {
   #  there is no transaction in progress by definition
   $self->{transaction_depth} = $self->_dbh_autocommit ? 0 : 1;
 
+  $self->_run_connection_actions unless $self->{_in_determine_driver};
+}
+
+sub _run_connection_actions {
+  my $self = shift;
   my @actions;
 
   push @actions, ( $self->on_connect_call || () );
@@ -763,16 +838,19 @@ sub _populate_dbh {
 sub _determine_driver {
   my ($self) = @_;
 
-  if (not $self->_driver_determined) {
+  if ((not $self->_driver_determined) && (not $self->{_in_determine_driver})) {
+    my $started_unconnected = 0;
+    local $self->{_in_determine_driver} = 1;
+
     if (ref($self) eq __PACKAGE__) {
       my $driver;
-
       if ($self->_dbh) { # we are connected
         $driver = $self->_dbh->{Driver}{Name};
       } else {
         # try to use dsn to not require being connected, the driver may still
         # force a connection in _rebless to determine version
         ($driver) = $self->_dbi_connect_info->[0] =~ /dbi:([^:]+):/i;
+        $started_unconnected = 1;
       }
 
       my $storage_class = "DBIx::Class::Storage::DBI::${driver}";
@@ -784,6 +862,9 @@ sub _determine_driver {
     }
 
     $self->_driver_determined(1);
+
+    $self->_run_connection_actions
+        if $started_unconnected && defined $self->_dbh;
   }
 }
 
@@ -983,14 +1064,17 @@ sub _svp_generate_name {
 
 sub txn_begin {
   my $self = shift;
-  $self->ensure_connected();
   if($self->{transaction_depth} == 0) {
     $self->debugobj->txn_begin()
       if $self->debug;
-    # this isn't ->_dbh-> because
-    #  we should reconnect on begin_work
-    #  for AutoCommit users
-    $self->dbh->begin_work;
+
+    # being here implies we have AutoCommit => 1
+    # if the user is utilizing txn_do - good for
+    # him, otherwise we need to ensure that the
+    # $dbh is healthy on BEGIN
+    my $dbh_method = $self->{_in_dbh_do} ? '_dbh' : 'dbh';
+    $self->$dbh_method->begin_work;
+
   } elsif ($self->auto_savepoint) {
     $self->svp_begin;
   }
@@ -1162,7 +1246,11 @@ sub insert {
       my $col_info = $source->column_info($col);
 
       if ( $col_info->{auto_nextval} ) {
-        $updated_cols->{$col} = $to_insert->{$col} = $self->_sequence_fetch( 'nextval', $col_info->{sequence} || $self->_dbh_get_autoinc_seq($self->dbh, $source) );
+        $updated_cols->{$col} = $to_insert->{$col} = $self->_sequence_fetch(
+          'nextval',
+          $col_info->{sequence} ||
+            $self->_dbh_get_autoinc_seq($self->_get_dbh, $source)
+        );
       }
     }
   }
@@ -1183,6 +1271,8 @@ sub insert_bulk {
   @colvalues{@$cols} = (0..$#$cols);
   my ($sql, @bind) = $self->sql_maker->insert($table, \%colvalues);
 
+  $self->_determine_driver;
+
   $self->_query_start( $sql, @bind );
   my $sth = $self->sth($sql);
 
@@ -1242,6 +1332,7 @@ sub insert_bulk {
 sub update {
   my $self = shift @_;
   my $source = shift @_;
+  $self->_determine_driver;
   my $bind_attributes = $self->source_bind_attributes($source);
 
   return $self->_execute('update' => [], $source, $bind_attributes, @_);
@@ -1251,7 +1342,7 @@ sub update {
 sub delete {
   my $self = shift @_;
   my $source = shift @_;
-
+  $self->_determine_driver;
   my $bind_attrs = $self->source_bind_attributes($source);
 
   return $self->_execute('delete' => [], $source, $bind_attrs, @_);
@@ -1510,9 +1601,9 @@ sub _adjust_select_args_for_complex_prefetch {
 
     # alias any functions to the dbic-side 'as' label
     # adjust the outer select accordingly
-    if (ref $sel eq 'HASH' && !$sel->{-select}) {
-      $sel = { -select => $sel, -as => $attrs->{as}[$i] };
-      $select->[$i] = join ('.', $attrs->{alias}, ($attrs->{as}[$i] || "select_$i") );
+    if (ref $sel eq 'HASH' ) {
+      $sel->{-as} ||= $attrs->{as}[$i];
+      $select->[$i] = join ('.', $attrs->{alias}, ($sel->{-as} || "select_$i") );
     }
 
     push @$sub_select, $sel;
@@ -1948,7 +2039,7 @@ Returns the database driver name.
 
 =cut
 
-sub sqlt_type { shift->dbh->{Driver}->{Name} }
+sub sqlt_type { shift->_get_dbh->{Driver}->{Name} }
 
 =head2 bind_attribute_by_data_type
 
@@ -2194,8 +2285,6 @@ See L<SQL::Translator/METHODS> for a list of values for C<$sqlt_args>.
 
 sub deployment_statements {
   my ($self, $schema, $type, $version, $dir, $sqltargs) = @_;
-  # Need to be connected to get the correct sqlt_type
-  $self->ensure_connected() unless $type;
   $type ||= $self->sqlt_type;
   $version ||= $schema->schema_version || '1.x';
   $dir ||= './';
@@ -2240,7 +2329,9 @@ sub deploy {
     return if $line =~ /^\s+$/; # skip whitespace only
     $self->_query_start($line);
     eval {
-      $self->dbh->do($line); # shouldn't be using ->dbh ?
+      # do a dbh_do cycle here, as we need some error checking in
+      # place (even though we will ignore errors)
+      $self->dbh_do (sub { $_[1]->do($line) });
     };
     if ($@) {
       carp qq{$@ (running "${line}")};
@@ -2269,7 +2360,7 @@ Returns the datetime parser class
 sub datetime_parser {
   my $self = shift;
   return $self->{datetime_parser} ||= do {
-    $self->ensure_connected;
+    $self->_populate_dbh unless $self->_dbh;
     $self->build_datetime_parser(@_);
   };
 }
@@ -2340,8 +2431,13 @@ sub lag_behind_master {
 
 sub DESTROY {
   my $self = shift;
-  return if !$self->_dbh;
-  $self->_verify_pid;
+  $self->_verify_pid if $self->_dbh;
+
+  # some databases need this to stop spewing warnings
+  if (my $dbh = $self->_dbh) {
+    eval { $dbh->disconnect };
+  }
+
   $self->_dbh(undef);
 }
 
@@ -2353,7 +2449,7 @@ sub DESTROY {
 
 DBIx::Class can do some wonderful magic with handling exceptions,
 disconnections, and transactions when you use C<< AutoCommit => 1 >>
-combined with C<txn_do> for transaction support.
+(the default) combined with C<txn_do> for transaction support.
 
 If you set C<< AutoCommit => 0 >> in your connect info, then you are always
 in an assumed transaction between commits, and you're telling us you'd
@@ -2365,7 +2461,6 @@ cases if you choose the C<< AutoCommit => 0 >> path, just as you would
 be with raw DBI.
 
 
-
 =head1 AUTHORS
 
 Matt S. Trout <mst@shadowcatsystems.co.uk>
index 9d88ed0..b0da553 100644 (file)
@@ -30,14 +30,14 @@ sub insert_bulk {
 
   if ($identity_insert) {
     my $table = $source->from;
-    $self->dbh->do("SET IDENTITY_INSERT $table ON");
+    $self->_get_dbh->do("SET IDENTITY_INSERT $table ON");
   }
 
   $self->next::method(@_);
 
   if ($identity_insert) {
     my $table = $source->from;
-    $self->dbh->do("SET IDENTITY_INSERT $table OFF");
+    $self->_get_dbh->do("SET IDENTITY_INSERT $table OFF");
   }
 }
 
@@ -68,7 +68,7 @@ sub insert {
     grep { not exists $to_insert->{$_} } (@pk_guids, @auto_guids);
 
   for my $guid_col (@get_guids_for) {
-    my ($new_guid) = $self->dbh->selectrow_array('SELECT NEWID()');
+    my ($new_guid) = $self->_get_dbh->selectrow_array('SELECT NEWID()');
     $updated_cols->{$guid_col} = $to_insert->{$guid_col} = $new_guid;
   }
 
@@ -145,7 +145,7 @@ sub last_insert_id { shift->_identity }
 sub _svp_begin {
   my ($self, $name) = @_;
 
-  $self->dbh->do("SAVE TRANSACTION $name");
+  $self->_get_dbh->do("SAVE TRANSACTION $name");
 }
 
 # A new SAVE TRANSACTION with the same name releases the previous one.
@@ -154,7 +154,7 @@ sub _svp_release { 1 }
 sub _svp_rollback {
   my ($self, $name) = @_;
 
-  $self->dbh->do("ROLLBACK TRANSACTION $name");
+  $self->_get_dbh->do("ROLLBACK TRANSACTION $name");
 }
 
 sub build_datetime_parser {
index 6f905af..d9b810a 100644 (file)
@@ -8,7 +8,8 @@ use mro 'c3';
 sub _rebless {
     my ($self) = @_;
 
-    my $dbtype = eval { $self->dbh->get_info(17) };
+    my $dbtype = eval { $self->_get_dbh->get_info(17) };
+
     unless ( $@ ) {
         # Translate the backend name into a perl identifier
         $dbtype =~ s/\W/_/gi;
index 77c20ad..5279502 100644 (file)
@@ -137,7 +137,7 @@ sub connect_call_use_server_cursors {
   my $self            = shift;
   my $sql_rowset_size = shift || 2;
 
-  $self->_dbh->{odbc_SQL_ROWSET_SIZE} = $sql_rowset_size;
+  $self->_get_dbh->{odbc_SQL_ROWSET_SIZE} = $sql_rowset_size;
 }
 
 =head2 connect_call_use_MARS
@@ -165,9 +165,9 @@ sub connect_call_use_MARS {
 
   if ($dsn !~ /MARS_Connection=/) {
     $self->_dbi_connect_info->[0] = "$dsn;MARS_Connection=Yes";
-    my $connected = defined $self->_dbh;
+    my $was_connected = defined $self->_dbh;
     $self->disconnect;
-    $self->ensure_connected if $connected;
+    $self->ensure_connected if $was_connected;
   }
 }
 
index 1bb9f79..7a49b50 100644 (file)
@@ -9,7 +9,7 @@ use mro 'c3';
 sub _rebless {
     my ($self) = @_;
 
-    my $version = eval { $self->dbh->get_info(18); };
+    my $version = eval { $self->_get_dbh->get_info(18); };
 
     if ( !$@ ) {
         my ($major, $minor, $patchlevel) = split(/\./, $version);
index 1593978..b97e34f 100644 (file)
@@ -76,7 +76,7 @@ sub _dbh_get_autoinc_seq {
 
 sub _sequence_fetch {
   my ( $self, $type, $seq ) = @_;
-  my ($id) = $self->dbh->selectrow_array("SELECT ${seq}.${type} FROM DUAL");
+  my ($id) = $self->_get_dbh->selectrow_array("SELECT ${seq}.${type} FROM DUAL");
   return $id;
 }
 
@@ -209,7 +209,7 @@ sub connect_call_datetime_setup {
 sub _svp_begin {
     my ($self, $name) = @_;
 
-    $self->dbh->do("SAVEPOINT $name");
+    $self->_get_dbh->do("SAVEPOINT $name");
 }
 
 =head2 source_bind_attributes
@@ -263,7 +263,7 @@ sub _svp_release { 1 }
 sub _svp_rollback {
     my ($self, $name) = @_;
 
-    $self->dbh->do("ROLLBACK TO SAVEPOINT $name")
+    $self->_get_dbh->do("ROLLBACK TO SAVEPOINT $name")
 }
 
 =head1 AUTHOR
index baf30fb..bea44c8 100644 (file)
@@ -15,7 +15,7 @@ warn "DBD::Pg 2.9.2 or greater is strongly recommended\n"
 sub with_deferred_fk_checks {
   my ($self, $sub) = @_;
 
-  $self->dbh->do('SET CONSTRAINTS ALL DEFERRED');
+  $self->_get_dbh->do('SET CONSTRAINTS ALL DEFERRED');
   $sub->();
 }
 
@@ -89,8 +89,16 @@ sub get_autoinc_seq {
   my ($self,$source,$col) = @_;
 
   my @pri = $source->primary_columns;
-  my ($schema,$table) = $source->name =~ /^(.+)\.(.+)$/ ? ($1,$2)
-    : (undef,$source->name);
+
+  my $schema;
+  my $table = $source->name;
+
+  if (ref $table eq 'SCALAR') {
+    $table = $$table;
+  }
+  elsif ($table =~ /^(.+)\.(.+)$/) {
+    ($schema, $table) = ($1, $2);
+  }
 
   $self->dbh_do('_dbh_get_autoinc_seq', $schema, $table, @pri);
 }
@@ -119,26 +127,26 @@ sub bind_attribute_by_data_type {
 
 sub _sequence_fetch {
   my ( $self, $type, $seq ) = @_;
-  my ($id) = $self->dbh->selectrow_array("SELECT nextval('${seq}')");
+  my ($id) = $self->_get_dbh->selectrow_array("SELECT nextval('${seq}')");
   return $id;
 }
 
 sub _svp_begin {
     my ($self, $name) = @_;
 
-    $self->dbh->pg_savepoint($name);
+    $self->_get_dbh->pg_savepoint($name);
 }
 
 sub _svp_release {
     my ($self, $name) = @_;
 
-    $self->dbh->pg_release($name);
+    $self->_get_dbh->pg_release($name);
 }
 
 sub _svp_rollback {
     my ($self, $name) = @_;
 
-    $self->dbh->pg_rollback_to($name);
+    $self->_get_dbh->pg_rollback_to($name);
 }
 
 1;
index 0a2cfb8..41b0c81 100644 (file)
@@ -12,7 +12,11 @@ use mro 'c3';
 sub _rebless {
     my $self = shift;
 
-    my $dbtype = eval { @{$self->dbh->selectrow_arrayref(qq{sp_server_info \@attribute_id=1})}[2] };
+    my $dbtype = eval {
+      @{$self->_get_dbh
+        ->selectrow_arrayref(qq{sp_server_info \@attribute_id=1})
+      }[2]
+    };
     unless ( $@ ) {
         $dbtype =~ s/\W/_/gi;
         my $subclass = "DBIx::Class::Storage::DBI::Sybase::${dbtype}";
index 5a98813..757d4d9 100644 (file)
@@ -29,7 +29,7 @@ sub _ping {
 
 sub _placeholders_supported {
   my $self = shift;
-  my $dbh  = $self->_dbh;
+  my $dbh  = $self->_get_dbh;
 
   return eval {
 # There's also $dbh->{syb_dynamic_supported} but it can be inaccurate for this
index 5e53118..6a20ba4 100644 (file)
@@ -11,7 +11,7 @@ use mro 'c3';
 
 sub _rebless {
   my $self = shift;
-  my $dbh  = $self->_dbh;
+  my $dbh  = $self->_get_dbh;
 
   if (not $self->_placeholders_supported) {
     bless $self,
index bce1a07..6224d53 100644 (file)
@@ -40,28 +40,28 @@ sub sqlt_type {
 sub _svp_begin {
     my ($self, $name) = @_;
 
-    $self->dbh->do("SAVEPOINT $name");
+    $self->_get_dbh->do("SAVEPOINT $name");
 }
 
 sub _svp_release {
     my ($self, $name) = @_;
 
-    $self->dbh->do("RELEASE SAVEPOINT $name");
+    $self->_get_dbh->do("RELEASE SAVEPOINT $name");
 }
 
 sub _svp_rollback {
     my ($self, $name) = @_;
 
-    $self->dbh->do("ROLLBACK TO SAVEPOINT $name")
+    $self->_get_dbh->do("ROLLBACK TO SAVEPOINT $name")
 }
 
 sub is_replicating {
-    my $status = shift->dbh->selectrow_hashref('show slave status');
+    my $status = shift->_get_dbh->selectrow_hashref('show slave status');
     return ($status->{Slave_IO_Running} eq 'Yes') && ($status->{Slave_SQL_Running} eq 'Yes');
 }
 
 sub lag_behind_master {
-    return shift->dbh->selectrow_hashref('show slave status')->{Seconds_Behind_Master};
+    return shift->_get_dbh->selectrow_hashref('show slave status')->{Seconds_Behind_Master};
 }
 
 # MySql can not do subquery update/deletes, only way is slow per-row operations.
@@ -81,7 +81,7 @@ DBIx::Class::Storage::DBI::mysql - Storage::DBI class implementing MySQL specifi
 Storage::DBI autodetects the underlying MySQL database, and re-blesses the
 C<$storage> object into this class.
 
-  my $schema = MyDb::Schema->connect( $dsn, $user, $pass, { set_strict_mode => 1 } );
+  my $schema = MyDb::Schema->connect( $dsn, $user, $pass, { on_connect_call => 'set_strict_mode' } );
 
 =head1 DESCRIPTION
 
index 9f45d1b..2ee47d5 100644 (file)
@@ -82,8 +82,9 @@ sub parse {
         my $source = $dbicschema->source($moniker);
         my $table_name = $source->name;
 
-        # Skip custom query sources
-        next if ref $table_name;
+        # FIXME - this isn't the right way to do it, but sqlt does not
+        # support quoting properly to be signaled about this
+        $table_name = $$table_name if ref $table_name eq 'SCALAR';
 
         # Its possible to have multiple DBIC sources using the same table
         next if $tables{$table_name};
@@ -142,6 +143,10 @@ sub parse {
             my $othertable = $source->related_source($rel);
             my $rel_table = $othertable->name;
 
+            # FIXME - this isn't the right way to do it, but sqlt does not
+            # support quoting properly to be signaled about this
+            $rel_table = $$rel_table if ref $rel_table eq 'SCALAR';
+
             my $reverse_rels = $source->reverse_relationship_info($rel);
             my ($otherrelname, $otherrelationship) = each %{$reverse_rels};
 
@@ -251,8 +256,22 @@ sub parse {
     ) {
       $schema->add_table ($tables{$table}{object});
       $tables{$table}{source} -> _invoke_sqlt_deploy_hook( $tables{$table}{object} );
-    }
 
+      # the hook might have already removed the table
+      if ($schema->get_table($table) && $table =~ /^ \s* \( \s* SELECT \s+/ix) {
+        warn <<'EOW';
+
+Custom SQL through ->name(\'( SELECT ...') is DEPRECATED, for more details see
+"Arbitrary SQL through a custom ResultSource" in DBIx::Class::Manual::Cookbook
+or http://search.cpan.org/dist/DBIx-Class/lib/DBIx/Class/Manual/Cookbook.pod
+
+EOW
+
+        # remove the table as there is no way someone might want to
+        # actually deploy this
+        $schema->drop_table ($table);
+      }
+    }
 
     my %views;
     foreach my $moniker (sort @view_monikers)
@@ -260,6 +279,10 @@ sub parse {
         my $source = $dbicschema->source($moniker);
         my $view_name = $source->name;
 
+        # FIXME - this isn't the right way to do it, but sqlt does not
+        # support quoting properly to be signaled about this
+        $view_name = $$view_name if ref $view_name eq 'SCALAR';
+
         # Skip custom query sources
         next if ref $view_name;
 
index c4768c8..8750d5a 100644 (file)
@@ -36,7 +36,7 @@ $rs = $schema->resultset('CD')->search(
 eval { $rs->count };
 is_same_sql_bind(
   $sql, \@bind,
-  "SELECT COUNT( * ) FROM `cd` `me`  JOIN `artist` `artist` ON ( `artist`.`artistid` = `me`.`artist` ) WHERE ( `artist`.`name` = ? AND `me`.`year` = ? )", ["'Caterwauler McCrae'", "'2001'"],
+  "SELECT COUNT( * ) FROM cd `me`  JOIN `artist` `artist` ON ( `artist`.`artistid` = `me`.`artist` ) WHERE ( `artist`.`name` = ? AND `me`.`year` = ? )", ["'Caterwauler McCrae'", "'2001'"],
   'got correct SQL for count query with quoting'
 );
 
@@ -60,7 +60,7 @@ $rs = $schema->resultset('CD')->search(
 eval { $rs->count };
 is_same_sql_bind(
   $sql, \@bind,
-  "SELECT COUNT( * ) FROM [cd] [me]  JOIN [artist] [artist] ON ( [artist].[artistid] = [me].[artist] ) WHERE ( [artist].[name] = ? AND [me].[year] = ? )", ["'Caterwauler McCrae'", "'2001'"],
+  "SELECT COUNT( * ) FROM cd [me]  JOIN [artist] [artist] ON ( [artist].[artistid] = [me].[artist] ) WHERE ( [artist].[name] = ? AND [me].[year] = ? )", ["'Caterwauler McCrae'", "'2001'"],
   'got correct SQL for count query with bracket quoting'
 );
 
index f0c34a9..3e7595a 100644 (file)
@@ -42,7 +42,7 @@ $rs = $schema->resultset('CD')->search(
 eval { $rs->count };
 is_same_sql_bind(
   $sql, \@bind,
-  "SELECT COUNT( * ) FROM `cd` `me`  JOIN `artist` `artist` ON ( `artist`.`artistid` = `me`.`artist` ) WHERE ( `artist`.`name` = ? AND `me`.`year` = ? )", ["'Caterwauler McCrae'", "'2001'"],
+  "SELECT COUNT( * ) FROM cd `me`  JOIN `artist` `artist` ON ( `artist`.`artistid` = `me`.`artist` ) WHERE ( `artist`.`name` = ? AND `me`.`year` = ? )", ["'Caterwauler McCrae'", "'2001'"],
   'got correct SQL for count query with quoting'
 );
 
@@ -73,7 +73,7 @@ $rs = $schema->resultset('CD')->search(
 eval { $rs->count };
 is_same_sql_bind(
   $sql, \@bind,
-  "SELECT COUNT( * ) FROM [cd] [me]  JOIN [artist] [artist] ON ( [artist].[artistid] = [me].[artist] ) WHERE ( [artist].[name] = ? AND [me].[year] = ? )", ["'Caterwauler McCrae'", "'2001'"],
+  "SELECT COUNT( * ) FROM cd [me]  JOIN [artist] [artist] ON ( [artist].[artistid] = [me].[artist] ) WHERE ( [artist].[name] = ? AND [me].[year] = ? )", ["'Caterwauler McCrae'", "'2001'"],
   'got correct SQL for count query with bracket quoting'
 );
 
index c494b74..d430398 100644 (file)
@@ -452,4 +452,6 @@ SKIP: {
     }
 }
 
+throws_ok { $schema->resultset} qr/resultset\(\) expects a source name/, 'resultset with no argument throws exception';
+
 done_testing;
index 3f635f8..3ba8579 100644 (file)
@@ -85,5 +85,6 @@ is_deeply($type_info, $test_type_info, 'columns_info_for - column data types');
 
 # clean up our mess
 END {
+    my $dbh = eval { $schema->storage->_dbh };
     $dbh->do("DROP TABLE artist") if $dbh;
 }
index 21c72df..359c13e 100644 (file)
@@ -82,6 +82,6 @@ is_deeply($type_info, $test_type_info, 'columns_info_for - column data types');
 
 # clean up our mess
 END {
+    my $dbh = eval { $schema->storage->_dbh };
     $dbh->do("DROP TABLE artist") if $dbh;
 }
-
index bee69e6..7560d2c 100644 (file)
@@ -64,6 +64,7 @@ my $cds = $schema->resultset ('CD')->search ({}, { order_by => 'me.cdid'}); # ma
 cmp_ok ($cds->count, '>', 2, 'Initially populated with more than 2 CDs');
 
 my $table = $cds->result_source->name;
+$table = $$table if ref $table eq 'SCALAR';
 my $subsel = $cds->search ({}, {
     columns => [qw/cdid title/],
     from => \ "(SELECT cdid, title FROM $table LIMIT 2) me",
index 1d9f17a..9ca9c2e 100644 (file)
@@ -10,8 +10,6 @@ use POSIX qw(ceil);
 
 my $schema = DBICTest->init_schema();
 
-plan tests => 1269;
-
 my $employees = $schema->resultset('Employee');
 $employees->delete();
 
@@ -42,11 +40,9 @@ foreach my $group_id (1..4) {
 my $group_3 = $employees->search({group_id=>3});
 my $to_group = 1;
 my $to_pos = undef;
-# now that we have transactions we need to work around stupid sqlite
 {
   my @empl = $group_3->all;
   while (my $employee = shift @empl) {
-    $employee->discard_changes;     # since we are effective shift()ing the $rs while doing this
     $employee->move_to_group($to_group, $to_pos);
     $to_pos++;
     $to_group = $to_group==1 ? 2 : 1;
@@ -54,7 +50,6 @@ my $to_pos = undef;
 }
 foreach my $group_id (1..4) {
     my $group_employees = $employees->search({group_id=>$group_id});
-    $group_employees->all();
     ok( check_rs($group_employees), "group positions after move_to_group" );
 }
 
@@ -129,7 +124,6 @@ my $to_group_2_base = 7;
 my $to_group_2 = 1;
 $to_pos = undef;
 
-# now that we have transactions we need to work around stupid sqlite
 {
   my @empl = $group_3->all;
   while (my $employee = shift @empl) {
@@ -143,7 +137,6 @@ $to_pos = undef;
 foreach my $group_id_2 (1..4) {
     foreach my $group_id_3 (1..4) {
         my $group_employees = $employees->search({group_id_2=>$group_id_2,group_id_3=>$group_id_3});
-        $group_employees->all();
         ok( check_rs($group_employees), "group positions after move_to_group" );
     }
 }
@@ -275,3 +268,4 @@ sub check_rs {
     return 1;
 }
 
+done_testing;
index 94bdfd3..c8a0bba 100644 (file)
@@ -93,6 +93,7 @@ my $invocations = {
           'bar',
           undef,
           {
+            %{$storage->_default_dbi_connect_attributes || {} },
             PrintError => 0,
             AutoCommit => 1,
           },
@@ -122,8 +123,8 @@ my $invocations = {
       args => [
           {
             on_connect_do => [qw/a b c/],
-            PrintError => 0,
-            AutoCommit => 1,
+            PrintError => 1,
+            AutoCommit => 0,
             on_disconnect_do => [qw/d e f/],
             user => 'bar',
             dsn => 'foo',
@@ -138,8 +139,9 @@ my $invocations = {
           'bar',
           undef,
           {
-            PrintError => 0,
-            AutoCommit => 1,
+            %{$storage->_default_dbi_connect_attributes || {} },
+            PrintError => 1,
+            AutoCommit => 0,
           },
       ],
   },
diff --git a/t/92storage_ping_count.t b/t/92storage_ping_count.t
new file mode 100644 (file)
index 0000000..07659cb
--- /dev/null
@@ -0,0 +1,61 @@
+use strict;
+use warnings;  
+
+use Test::More;
+use lib qw(t/lib);
+use DBICTest;
+use Data::Dumper;
+use DBIC::SqlMakerTest;
+
+my $ping_count = 0;
+
+{
+  local $SIG{__WARN__} = sub {};
+  require DBIx::Class::Storage::DBI;
+
+  my $ping = \&DBIx::Class::Storage::DBI::_ping;
+
+  *DBIx::Class::Storage::DBI::_ping = sub {
+    $ping_count++;
+    goto &$ping;
+  };
+}
+
+
+# measure pings around deploy() separately
+my $schema = DBICTest->init_schema( sqlite_use_file => 1, no_populate => 1 );
+
+is ($ping_count, 0, 'no _ping() calls during deploy');
+$ping_count = 0;
+
+
+
+DBICTest->populate_schema ($schema);
+
+# perform some operations and make sure they don't ping
+
+$schema->resultset('CD')->create({
+  cdid => 6, artist => 3, title => 'mtfnpy', year => 2009
+});
+
+$schema->resultset('CD')->create({
+  cdid => 7, artist => 3, title => 'mtfnpy2', year => 2009
+});
+
+$schema->storage->_dbh->disconnect;
+
+$schema->resultset('CD')->create({
+  cdid => 8, artist => 3, title => 'mtfnpy3', year => 2009
+});
+
+$schema->storage->_dbh->disconnect;
+
+$schema->txn_do(sub {
+ $schema->resultset('CD')->create({
+   cdid => 9, artist => 3, title => 'mtfnpy4', year => 2009
+ });
+});
+
+is $ping_count, 0, 'no _ping() calls';
+
+done_testing;
index ea77526..e6ee0eb 100644 (file)
@@ -65,5 +65,6 @@ is( $it->next, undef, "next past end of resultset ok" );
 
 # clean up our mess
 END {
+    my $dbh = eval { $schema->storage->_dbh };
     $dbh->do("DROP TABLE artist") if $dbh;
 }
index 9b6f1bf..1cd2d90 100644 (file)
@@ -11,9 +11,7 @@ use IO::Handle;
 
 BEGIN {
     eval "use DBIx::Class::Storage::DBI::Replicated; use Test::Moose";
-    plan $@
-        ? ( skip_all => "Deps not installed: $@" )
-        : ( tests => 126 );
+    plan skip_all => "Deps not installed: $@" if $@;
 }
 
 use_ok 'DBIx::Class::Storage::DBI::Replicated::Pool';
@@ -766,6 +764,20 @@ is $debug{storage_type}, 'REPLICANT', "got last query from a replicant: $debug{d
 
     is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
 
+    ok $artist->discard_changes({force_pool=>'master'})
+       => 'properly called discard_changes against master (manual attrs)';
+
+    is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
+
+    ok $artist->discard_changes()
+       => 'properly called discard_changes against master (default attrs)';
+
+    is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
+
+    ok $artist->discard_changes({force_pool=>$replicant_names[0]})
+       => 'properly able to override the default attributes';
+
+    is $debug{storage_type}, 'REPLICANT', "got last query from a replicant: $debug{dsn}"
 }
 
 ## Test some edge cases, like trying to do a transaction inside a transaction, etc
@@ -836,4 +848,6 @@ is $debug{storage_type}, 'REPLICANT', "got last query from a replicant: $debug{d
 ## Delete the old database files
 $replicated->cleanup;
 
+done_testing;
+
 # vim: sw=4 sts=4 :
index 6b27c7b..e7fbb60 100644 (file)
@@ -49,9 +49,7 @@ my ($sql, @bind) = $sql_maker->select(
           [
             'me.cdid',
             { count => 'tracks.cd' },
-            { -select => 'me.artist' },
-            { -select => 'me.title', -as => 'name' },
-            { -select => { min => 'me.year' }, -as => 'me.minyear' },
+            { min => 'me.year', -as => 'me.minyear' },
           ],
           {
             'artist.name' => 'Caterwauler McCrae',
@@ -65,7 +63,7 @@ my ($sql, @bind) = $sql_maker->select(
 is_same_sql_bind(
   $sql, \@bind,
   q/
-    SELECT `me`.`cdid`, COUNT( `tracks`.`cd` ), `me`.`artist`, `me`.`title` AS `name`, MIN( `me`.`year` ) AS `me`.`minyear`
+    SELECT `me`.`cdid`, COUNT( `tracks`.`cd` ), MIN( `me`.`year` ) AS `me`.`minyear`
       FROM `cd` `me`
       JOIN `artist` `artist` ON ( `artist`.`artistid` = `me`.`artist` )
       LEFT JOIN `tracks` `tracks` ON ( `tracks`.`cd` = `me`.`cdid` )
@@ -294,7 +292,13 @@ $sql_maker->quote_char([qw/[ ]/]);
           ],
           [
             {
-              'count' => '*'
+              max => 'rank',
+              -as => 'max_rank',
+            },
+            'rank',
+            {
+              'count' => '*',
+              -as => 'cnt',
             }
           ],
           {
@@ -308,7 +312,7 @@ $sql_maker->quote_char([qw/[ ]/]);
 
 is_same_sql_bind(
   $sql, \@bind,
-  q/SELECT COUNT( * ) FROM [cd] [me]  JOIN [artist] [artist] ON ( [artist].[artistid] = [me].[artist] ) WHERE ( [artist].[name] = ? AND [me].[year] = ? )/, [ ['artist.name' => 'Caterwauler McCrae'], ['me.year' => 2001] ],
+  q/SELECT MAX ( [rank] ) AS [max_rank], [rank], COUNT( * ) AS [cnt] FROM [cd] [me]  JOIN [artist] [artist] ON ( [artist].[artistid] = [me].[artist] ) WHERE ( [artist].[name] = ? AND [me].[year] = ? )/, [ ['artist.name' => 'Caterwauler McCrae'], ['me.year' => 2001] ],
   'got correct SQL and bind parameters for count query with bracket quoting'
 );
 
index bbae42d..5bbd302 100644 (file)
@@ -29,7 +29,7 @@ plan tests => ( @sources * 3);
        my $sqlt_schema = create_schema({ schema => $schema, args => { parser_args => { } } });
 
        foreach my $source (@sources) {
-               my $table = $sqlt_schema->get_table($schema->source($source)->from);
+               my $table = get_table($sqlt_schema, $schema, $source);
 
                my $fk_count = scalar(grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints);
                my @indices = $table->get_indices;
@@ -43,7 +43,7 @@ plan tests => ( @sources * 3);
        my $sqlt_schema = create_schema({ schema => $schema, args => { parser_args => { add_fk_index => 1 } } });
 
        foreach my $source (@sources) {
-               my $table = $sqlt_schema->get_table($schema->source($source)->from);
+               my $table = get_table($sqlt_schema, $schema, $source);
 
                my $fk_count = scalar(grep { $_->type eq 'FOREIGN KEY' } $table->get_constraints);
                my @indices = $table->get_indices;
@@ -57,7 +57,7 @@ plan tests => ( @sources * 3);
        my $sqlt_schema = create_schema({ schema => $schema, args => { parser_args => { add_fk_index => 0 } } });
 
        foreach my $source (@sources) {
-               my $table = $sqlt_schema->get_table($schema->source($source)->from);
+               my $table = get_table($sqlt_schema, $schema, $source);
 
                my @indices = $table->get_indices;
                my $index_count = scalar(@indices);
@@ -83,3 +83,12 @@ sub create_schema {
        $sqlt->parser('SQL::Translator::Parser::DBIx::Class');
        return $sqlt->translate({ data => $schema }) or die $sqlt->error;
 }
+
+sub get_table {
+    my ($sqlt_schema, $schema, $source) = @_;
+
+    my $table_name = $schema->source($source)->from;
+    $table_name    = $$table_name if ref $table_name;
+
+    return $sqlt_schema->get_table($table_name);
+}
index 6df9ed0..b93f9ad 100644 (file)
@@ -11,8 +11,6 @@ use DBIC::SqlMakerTest;
 
 my $schema = DBICTest->init_schema();
 
-plan tests => 56;
-
 # The tag Blue is assigned to cds 1 2 3 and 5
 # The tag Cheesy is assigned to cds 2 4 and 5
 #
@@ -86,5 +84,34 @@ throws_ok(
   'throw on unsupported syntax'
 );
 
+# make sure distinct+func works
+{
+  my $rs = $schema->resultset('Artist')->search(
+    {},
+    {
+      join => 'cds',
+      distinct => 1,
+      '+select' => [ { count => 'cds.cdid', -as => 'amount_of_cds' } ],
+      '+as' => [qw/num_cds/],
+      order_by => { -desc => 'amount_of_cds' },
+    }
+  );
+
+  is_same_sql_bind (
+    $rs->as_query,
+    '(
+      SELECT me.artistid, me.name, me.rank, me.charfield, COUNT( cds.cdid ) AS amount_of_cds
+        FROM artist me LEFT JOIN cd cds ON cds.artist = me.artistid
+      GROUP BY me.artistid, me.name, me.rank, me.charfield
+      ORDER BY amount_of_cds DESC
+    )',
+    [],
+  );
+
+  is ($rs->next->get_column ('num_cds'), 3, 'Function aliased correctly');
+}
+
 # These two rely on the database to throw an exception. This might not be the case one day. Please revise.
 dies_ok(sub { my $count = $schema->resultset('Tag')->search({}, { '+select' => \'tagid AS tag_id', distinct => 1 })->count }, 'expecting to die');
+
+done_testing;
index cb5bcaf..b98801e 100644 (file)
@@ -1,7 +1,8 @@
 use strict;
-use warnings;  
+use warnings;
 
 use Test::More;
+use Test::Exception;
 use lib qw(t/lib);
 use DBICTest;
 
@@ -10,8 +11,6 @@ my $schema = DBICTest->init_schema();
 eval { require DateTime };
 plan skip_all => "Need DateTime for inflation tests" if $@;
 
-plan tests => 22;
-
 $schema->class('CD') ->inflate_column( 'year',
     { inflate => sub { DateTime->new( year => shift ) },
       deflate => sub { shift->year } }
@@ -54,10 +53,10 @@ eval { $cd->set_inflated_column('year', $now) };
 ok(!$@, 'set_inflated_column with DateTime object');
 $cd->update;
 
-$cd = $schema->resultset("CD")->find(3);                 
+$cd = $schema->resultset("CD")->find(3);
 is( $cd->year->year, $now->year, 'deflate ok' );
 
-$cd = $schema->resultset("CD")->find(3);                 
+$cd = $schema->resultset("CD")->find(3);
 my $before_year = $cd->year->year;
 eval { $cd->set_inflated_column('year', \'year + 1') };
 ok(!$@, 'set_inflated_column to "year + 1"');
@@ -66,18 +65,17 @@ $cd->update;
 TODO: {
   local $TODO = 'this was left in without a TODO - should it work?';
 
-  eval {
+  lives_ok (sub {
     $cd->store_inflated_column('year', \'year + 1');
     is_deeply( $cd->year, \'year + 1', 'deflate ok' );
-  };
-  ok(!$@, 'store_inflated_column to "year + 1"');
+  }, 'store_inflated_column to "year + 1"');
 }
 
-$cd = $schema->resultset("CD")->find(3);                 
+$cd = $schema->resultset("CD")->find(3);
 is( $cd->year->year, $before_year+1, 'deflate ok' );
 
 # store_inflated_column test
-$cd = $schema->resultset("CD")->find(3);                 
+$cd = $schema->resultset("CD")->find(3);
 eval { $cd->store_inflated_column('year', $now) };
 ok(!$@, 'store_inflated_column with DateTime object');
 $cd->update;
@@ -85,21 +83,21 @@ $cd->update;
 is( $cd->year->year, $now->year, 'deflate ok' );
 
 # update tests
-$cd = $schema->resultset("CD")->find(3);                 
+$cd = $schema->resultset("CD")->find(3);
 eval { $cd->update({'year' => $now}) };
 ok(!$@, 'update using DateTime object ok');
 is($cd->year->year, $now->year, 'deflate ok');
 
-$cd = $schema->resultset("CD")->find(3);                 
+$cd = $schema->resultset("CD")->find(3);
 $before_year = $cd->year->year;
 eval { $cd->update({'year' => \'year + 1'}) };
 ok(!$@, 'update using scalarref ok');
 
-$cd = $schema->resultset("CD")->find(3);                 
+$cd = $schema->resultset("CD")->find(3);
 is($cd->year->year, $before_year + 1, 'deflate ok');
 
 # discard_changes test
-$cd = $schema->resultset("CD")->find(3);                 
+$cd = $schema->resultset("CD")->find(3);
 # inflate the year
 $before_year = $cd->year->year;
 $cd->update({ year => \'year + 1'});
@@ -110,4 +108,5 @@ is($cd->year->year, $before_year + 1, 'discard_changes clears the inflated value
 my $copy = $cd->copy({ year => $now, title => "zemoose" });
 
 isnt( $copy->year->year, $before_year, "copy" );
+
+done_testing;
index c2be971..e9b51df 100644 (file)
@@ -10,13 +10,13 @@ my $schema = DBICTest->init_schema();
 use Data::Dumper;
 
 my @serializers = (
-    {  module => 'YAML.pm',
-       inflater => sub { YAML::Load (shift) },
-       deflater => sub { die "Expecting a reference" unless (ref $_[0]); YAML::Dump (shift) },
+    { module => 'YAML.pm',
+      inflater => sub { YAML::Load (shift) },
+      deflater => sub { die "Expecting a reference" unless (ref $_[0]); YAML::Dump (shift) },
     },
-    {  module => 'Storable.pm',
-       inflater => sub { Storable::thaw (shift) },
-       deflater => sub { die "Expecting a reference" unless (ref $_[0]); Storable::nfreeze (shift) },
+    { module => 'Storable.pm',
+      inflater => sub { Storable::thaw (shift) },
+      deflater => sub { die "Expecting a reference" unless (ref $_[0]); Storable::nfreeze (shift) },
     },
 );
 
@@ -25,14 +25,13 @@ my $selected;
 foreach my $serializer (@serializers) {
     eval { require $serializer->{module} };
     unless ($@) {
-       $selected = $serializer;
-       last;
+      $selected = $serializer;
+      last;
     }
 }
 
 plan (skip_all => "No suitable serializer found") unless $selected;
 
-plan (tests => 11);
 DBICTest::Schema::Serialized->inflate_column( 'serialized',
     { inflate => $selected->{inflater},
       deflate => $selected->{deflater},
@@ -42,17 +41,17 @@ Class::C3->reinitialize;
 
 my $struct_hash = {
     a => 1,
-    b => [ 
+    b => [
         { c => 2 },
     ],
     d => 3,
 };
 
 my $struct_array = [
-    'a', 
-    { 
-       b => 1,
-       c => 2
+    'a',
+    {
+      b => 1,
+      c => 2,
     },
     'd',
 ];
@@ -63,7 +62,6 @@ my $inflated;
 #======= testing hashref serialization
 
 my $object = $rs->create( { 
-    id => 1,
     serialized => '',
 } );
 ok($object->update( { serialized => $struct_hash } ), 'hashref deflation');
@@ -71,13 +69,19 @@ ok($inflated = $object->serialized, 'hashref inflation');
 is_deeply($inflated, $struct_hash, 'inflated hash matches original');
 
 $object = $rs->create( { 
-    id => 2,
     serialized => '',
 } );
-eval { $object->set_inflated_column('serialized', $struct_hash) };
-ok(!$@, 'set_inflated_column to a hashref');
+$object->set_inflated_column('serialized', $struct_hash);
 is_deeply($object->serialized, $struct_hash, 'inflated hash matches original');
 
+$object = $rs->new({});
+$object->serialized ($struct_hash);
+$object->insert;
+is_deeply (
+  $rs->find ({id => $object->id})->serialized,
+  $struct_hash,
+  'new/insert works',
+);
 
 #====== testing arrayref serialization
 
@@ -85,8 +89,16 @@ ok($object->update( { serialized => $struct_array } ), 'arrayref deflation');
 ok($inflated = $object->serialized, 'arrayref inflation');
 is_deeply($inflated, $struct_array, 'inflated array matches original');
 
+$object = $rs->new({});
+$object->serialized ($struct_array);
+$object->insert;
+is_deeply (
+  $rs->find ({id => $object->id})->serialized,
+  $struct_array,
+  'new/insert works',
+);
 
-#===== make sure make_column_dirty ineracts reasonably with inflation
+#===== make sure make_column_dirty interacts reasonably with inflation
 $object = $rs->first;
 $object->update ({serialized => { x => 'y'}});
 
@@ -98,3 +110,5 @@ $object->make_column_dirty('serialized');
 $object->update;
 
 is_deeply ($rs->first->serialized, { x => 'z' }, 'changes made it to the db' );
+
+done_testing;
index c69d229..ee55792 100644 (file)
@@ -135,7 +135,7 @@ sub deploy_schema {
         close IN;
         for my $chunk ( split (/;\s*\n+/, $sql) ) {
           if ( $chunk =~ / ^ (?! --\s* ) \S /xm ) {  # there is some real sql in the chunk - a non-space at the start of the string which is not a comment
-            $schema->storage->dbh->do($chunk) or print "Error on SQL: $chunk\n";
+            $schema->storage->dbh_do(sub { $_[1]->do($chunk) }) or print "Error on SQL: $chunk\n";
           }
         }
     }
index 80af1df..1463d00 100644 (file)
@@ -3,7 +3,10 @@ package # hide from PAUSE
 
 use base qw/DBICTest::BaseResult/;
 
-__PACKAGE__->table('cd');
+# this tests table name as scalar ref
+# DO NOT REMOVE THE \
+__PACKAGE__->table(\'cd');
+
 __PACKAGE__->add_columns(
   'cdid' => {
     data_type => 'integer',
index d169d72..bdad8b8 100644 (file)
@@ -12,4 +12,6 @@ __PACKAGE__->result_source_instance->name(\<<SQL);
   WHERE cd.year = ?)
 SQL
 
+sub sqlt_deploy_hook { $_[1]->schema->drop_table($_[1]) }
+
 1;
index 92c210f..d7737bd 100644 (file)
@@ -5,7 +5,7 @@ use base qw/DBICTest::BaseResult/;
 
 __PACKAGE__->table('serialized');
 __PACKAGE__->add_columns(
-  'id' => { data_type => 'integer' },
+  'id' => { data_type => 'integer', is_auto_increment => 1 },
   'serialized' => { data_type => 'text' },
 );
 __PACKAGE__->set_primary_key('id');
index 20b8e5a..86e345e 100644 (file)
@@ -1,6 +1,6 @@
 -- 
 -- Created by SQL::Translator::Producer::SQLite
--- Created on Thu Jul 30 09:37:43 2009
+-- Created on Wed Aug 12 16:10:43 2009
 -- 
 
 
diff --git a/t/multi_create/diamond.t b/t/multi_create/diamond.t
new file mode 100644 (file)
index 0000000..499f7a1
--- /dev/null
@@ -0,0 +1,52 @@
+use strict;
+use warnings;
+
+use Test::More;
+use Test::Exception;
+use lib qw(t/lib);
+use DBICTest;
+
+sub mc_diag { diag (@_) if $ENV{DBIC_MULTICREATE_DEBUG} };
+
+my $schema = DBICTest->init_schema();
+
+mc_diag (<<'DG');
+* Try a diamond multicreate
+
+Artist -> has_many -> Artwork_to_Artist -> belongs_to
+                                               /
+  belongs_to <- CD <- belongs_to <- Artwork <-/
+    \
+     \-> Artist2
+
+DG
+
+lives_ok (sub {
+  $schema->resultset ('Artist')->create ({
+    name => 'The wooled wolf',
+    artwork_to_artist => [{
+      artwork => {
+        cd => {
+          title => 'Wool explosive',
+          year => 1999,
+          artist => { name => 'The black exploding sheep' },
+        }
+      }
+    }],
+  });
+
+  my $art2 = $schema->resultset ('Artist')->find ({ name => 'The black exploding sheep' });
+  ok ($art2, 'Second artist exists');
+
+  my $cd = $art2->cds->single;
+  is ($cd->title, 'Wool explosive', 'correctly created CD');
+
+  is_deeply (
+    [ $cd->artwork->artists->get_column ('name')->all ],
+    [ 'The wooled wolf' ],
+    'Artist correctly attached to artwork',
+  );
+
+}, 'Diamond chain creation ok');
+
+done_testing;
diff --git a/t/multi_create/existing_in_chain.t b/t/multi_create/existing_in_chain.t
new file mode 100644 (file)
index 0000000..825fad4
--- /dev/null
@@ -0,0 +1,105 @@
+use strict;
+use warnings;
+
+use Test::More;
+use Test::Exception;
+use lib qw(t/lib);
+use DBICTest;
+
+my $schema = DBICTest->init_schema();
+
+# For fully intuitive multicreate any relationships in a chain
+# that do not exist for one reason or another should be created,
+# even if the preceeding relationship already exists.
+#
+# To get this to work a minor rewrite of find() is necessary, and
+# more importantly some sort of recursive_insert() call needs to 
+# be available. The way things will work then is:
+# *) while traversing the hierarchy code calls find_or_create()
+# *) this in turn calls find(%\nested_dataset)
+# *) this should return not only the existing object, but must
+#    also attach all non-existing (in fact maybe existing) related
+#    bits of data to it, with in_storage => 0
+# *) then before returning the result of the succesful find(), we
+#    simply call $obj->recursive_insert and all is dandy
+#
+# Since this will not be a very clean solution, todoifying for the
+# time being until an actual need arises
+#
+# ribasushi
+
+TODO: { my $f = __FILE__; local $TODO = "See comment at top of $f for discussion of the TODO";
+
+{
+  my $counts;
+  $counts->{$_} = $schema->resultset($_)->count for qw/Track CD Genre/;
+
+  lives_ok (sub {
+    my $existing_nogen_cd = $schema->resultset('CD')->search (
+      { 'genre.genreid' => undef },
+      { join => 'genre' },
+    )->first;
+
+    $schema->resultset('Track')->create ({
+      title => 'Sugar-coated',
+      cd => {
+        title => $existing_nogen_cd->title,
+        genre => {
+          name => 'sugar genre',
+        }
+      }
+    });
+
+    is ($schema->resultset('Track')->count, $counts->{Track} + 1, '1 new track');
+    is ($schema->resultset('CD')->count, $counts->{CD}, 'No new cds');
+    is ($schema->resultset('Genre')->count, $counts->{Genre} + 1, '1 new genre');
+
+    is ($existing_nogen_cd->genre->title,  'sugar genre', 'Correct genre assigned to CD');
+  }, 'create() did not throw');
+}
+{
+  my $counts;
+  $counts->{$_} = $schema->resultset($_)->count for qw/Artist CD Producer/;
+
+  lives_ok (sub {
+    my $artist = $schema->resultset('Artist')->first;
+    my $producer = $schema->resultset('Producer')->create ({ name => 'the queen of england' });
+
+    $schema->resultset('CD')->create ({
+      artist => $artist,
+      title => 'queen1',
+      year => 2007,
+      cd_to_producer => [
+        {
+          producer => {
+          name => $producer->name,
+            producer_to_cd => [
+              {
+                cd => {
+                  title => 'queen2',
+                  year => 2008,
+                  artist => $artist,
+                },
+              },
+            ],
+          },
+        },
+      ],
+    });
+
+    is ($schema->resultset('Artist')->count, $counts->{Artist}, 'No new artists');
+    is ($schema->resultset('Producer')->count, $counts->{Producer} + 1, '1 new producers');
+    is ($schema->resultset('CD')->count, $counts->{CD} + 2, '2 new cds');
+
+    is ($producer->cds->count, 2, 'CDs assigned to correct producer');
+    is_deeply (
+      [ $producer->cds->search ({}, { order_by => 'title' })->get_column('title')->all],
+      [ qw/queen1 queen2/ ],
+      'Correct cd names',
+    );
+  }, 'create() did not throw');
+}
+
+}
+
+done_testing;
diff --git a/t/multi_create/has_many.t b/t/multi_create/has_many.t
new file mode 100644 (file)
index 0000000..2d6818e
--- /dev/null
@@ -0,0 +1,33 @@
+use strict;
+use warnings;
+
+use Test::More;
+use Test::Exception;
+use lib qw(t/lib);
+use DBICTest;
+
+plan tests => 2;
+
+my $schema = DBICTest->init_schema();
+
+my $track_no_lyrics = $schema->resultset ('Track')
+              ->search ({ 'lyrics.lyric_id' => undef }, { join => 'lyrics' })
+                ->first;
+
+my $lyric = $track_no_lyrics->create_related ('lyrics', {
+  lyric_versions => [
+    { text => 'english doubled' },
+    { text => 'english doubled' },
+  ],
+});
+is ($lyric->lyric_versions->count, 2, "Two identical has_many's created");
+
+
+my $link = $schema->resultset ('Link')->create ({
+  url => 'lolcats!',
+  bookmarks => [
+    {},
+    {},
+  ]
+});
+is ($link->bookmarks->count, 2, "Two identical default-insert has_many's created");
index 07101a2..ef5c9a8 100644 (file)
@@ -8,8 +8,6 @@ use DBICTest;
 
 sub mc_diag { diag (@_) if $ENV{DBIC_MULTICREATE_DEBUG} };
 
-plan tests => 26;
-
 my $schema = DBICTest->init_schema();
 
 mc_diag (<<'DG');
@@ -102,4 +100,4 @@ for my $type (qw/has_one might_have/) {
   }, "multilevel $type with a PK == FK in the $type/has_many table ok");
 }
 
-1;
+done_testing;
index 9cd754f..57993cf 100644 (file)
@@ -6,7 +6,7 @@ use Test::Exception;
 use lib qw(t/lib);
 use DBICTest;
 
-plan tests => 93;
+plan tests => 91;
 
 my $schema = DBICTest->init_schema();
 
@@ -329,26 +329,6 @@ lives_ok ( sub {
 }, 'Nested find_or_create');
 
 lives_ok ( sub {
-  my $artist2 = $schema->resultset('Artist')->create({
-    name => 'Fred 4',
-    cds => [
-      {
-        title => 'Music to code by',
-        year => 2007,
-      },
-    ],
-    cds_unordered => [
-      {
-        title => 'Music to code by',
-        year => 2007,
-      },
-    ]
-  });
-
-  is($artist2->in_storage, 1, 'artist with duplicate rels inserted okay');
-}, 'Multiple same level has_many create');
-
-lives_ok ( sub {
        my $artist = $schema->resultset('Artist')->first;
        
        my $cd_result = $artist->create_related('cds', {
index 92f383c..8e07612 100644 (file)
@@ -133,10 +133,10 @@ for ($cd_rs->all) {
     },
     {
       prefetch => [qw/tracks liner_notes/],
-      select => ['me.cdid', { count => 'tracks.trackid' } ],
-      as => [qw/cdid track_count/],
+      select => ['me.cdid', { count => 'tracks.trackid' }, { max => 'tracks.trackid', -as => 'maxtr'} ],
+      as => [qw/cdid track_count max_track_id/],
       group_by => 'me.cdid',
-      order_by => { -desc => 'track_count' },
+      order_by => [ { -desc => 'track_count' }, { -asc => 'maxtr' } ],
       rows => 2,
     }
   );
@@ -162,22 +162,22 @@ for ($cd_rs->all) {
   is_same_sql_bind (
     $most_tracks_rs->as_query,
     '(
-      SELECT  me.cdid, me.track_count,
+      SELECT  me.cdid, me.track_count, me.maxtr,
               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
+          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 )
           GROUP BY me.cdid
-          ORDER BY track_count DESC
+          ORDER BY track_count DESC, maxtr ASC
           LIMIT 2
         ) me
         LEFT JOIN track tracks ON tracks.cd = me.cdid
         LEFT JOIN liner_notes liner_notes ON liner_notes.liner_id = me.cdid
       WHERE ( me.cdid IS NOT NULL )
-      ORDER BY track_count DESC, tracks.cd
+      ORDER BY track_count DESC, maxtr ASC, tracks.cd
     )',
     [],
     'next() query generated expected SQL',
@@ -235,4 +235,40 @@ for ($cd_rs->all) {
   is ($rs->count, 5, 'Correct count of CDs');
 }
 
+# RT 47779, test group_by as a scalar ref
+{
+  my $track_rs = $schema->resultset ('Track')->search (
+    { 'me.cd' => { -in => [ $cd_rs->get_column ('cdid')->all ] } },
+    {
+      select => [
+        'me.cd',
+        { count => 'me.trackid' },
+      ],
+      as => [qw/
+        cd
+        track_count
+      /],
+      group_by => \'SUBSTR(me.cd, 1, 1)',
+      prefetch => 'cd',
+    },
+  );
+
+  is_same_sql_bind (
+    $track_rs->count_rs->as_query,
+    '(
+      SELECT COUNT( * )
+        FROM (
+          SELECT SUBSTR(me.cd, 1, 1)
+            FROM track me
+            JOIN cd cd ON cd.cdid = me.cd
+          WHERE ( me.cd IN ( ?, ?, ?, ?, ? ) )
+          GROUP BY SUBSTR(me.cd, 1, 1)
+        )
+      count_subq
+    )',
+    [ map { [ 'me.cd' => $_] } ($cd_rs->get_column ('cdid')->all) ],
+    'count() query generated expected SQL',
+  );
+}
+
 done_testing;