Merge 'trunk' into 'sybase'
Peter Rabbitson [Sat, 8 Aug 2009 22:41:12 +0000 (22:41 +0000)]
r7219@Thesaurus (orig r7216):  ribasushi | 2009-08-05 10:38:14 +0200
Silence a TODO test
r7220@Thesaurus (orig r7217):  caelum | 2009-08-05 10:46:11 +0200
minor Changes update
r7230@Thesaurus (orig r7227):  castaway | 2009-08-05 14:57:52 +0200
Minty's conversion of cookbook "arbitrary sql" to use ResultSource::View, plus some examples in ::View itself.
Some style tweaks of mine

r7231@Thesaurus (orig r7228):  ribasushi | 2009-08-05 15:41:28 +0200
Dynamically load necessary table classes
r7236@Thesaurus (orig r7233):  caelum | 2009-08-05 19:49:51 +0200
fix rounding issues in mssql money tests
r7237@Thesaurus (orig r7234):  caelum | 2009-08-05 20:09:03 +0200
better money value comparison in tests
r7239@Thesaurus (orig r7236):  frew | 2009-08-05 20:53:32 +0200
whitespace jfklds;ajfklds;a
r7240@Thesaurus (orig r7237):  frew | 2009-08-05 20:54:41 +0200
Fix testing bug.  Windows only.
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

15 files changed:
Changes
Makefile.PL
lib/DBIx/Class/ResultSet.pm
lib/DBIx/Class/Storage/DBI.pm
lib/DBIx/Class/Storage/DBI/Cursor.pm
lib/DBIx/Class/Storage/DBI/NoBindVars.pm
lib/DBIx/Class/Storage/DBI/Oracle/Generic.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/Sybase/NoBindVars.pm [new file with mode: 0644]
t/746mssql.t
t/746sybase.t
t/inflate/datetime_sybase.t [new file with mode: 0644]
t/lib/sqlite.sql

diff --git a/Changes b/Changes
index 51cd72a..a7518cb 100644 (file)
--- a/Changes
+++ b/Changes
@@ -21,8 +21,17 @@ Revision history for DBIx::Class
         - Support for MSSQL 'money' type
         - Support for 'smalldatetime' type used in MSSQL and Sybase for
           InflateColumn::DateTime
-        - support for Postgres 'timestamp without timezone' type in
-          InflateColumn::DateTime (RT#48389)
+        - Support for Postgres 'timestamp without timezone' type in
+          InflateColumn::DateTime
+        - Much improved Sybase support, including support for TEXT/IMAGE
+          columns and connecting via FreeTDS
+        - Replication updates: Improved the replication tests so that they are
+          more reliable and accurate, and hopefully solve some cross platform
+          issues.  Bugfixes related to naming particular replicants in a
+          'force_pool' attribute.  Lots of documentation updates, including a
+          new Introduction.pod file. Fixed the way we detect transaction to 
+          make this more reliable and forward looking. Fixed some trouble with
+          the way Moose Types are used.
         - Added new MySQL specific on_connect_call macro 'set_strict_mode'
           (also known as make_mysql_not_suck_as_much)
         - Multiple prefetch-related fixes:
index 0473058..0b95667 100644 (file)
@@ -101,6 +101,12 @@ my %force_requires_if_author = (
       'DateTime::Format::Oracle' => 0,
     ) : ()
   ,
+
+  $ENV{DBICTEST_SYBASE_DSN}
+    ? (
+      'DateTime::Format::Sybase' => 0,
+    ) : ()
+  ,
 );
 
 
index e24dafe..e42e799 100644 (file)
@@ -2780,7 +2780,14 @@ sub _resolved_attrs {
                       : "${alias}.$_"
                   )
             }
-      } ( ref($attrs->{columns}) eq 'ARRAY' ) ? @{ delete $attrs->{columns}} : (delete $attrs->{columns} || $source->columns );
+      } ( ref($attrs->{columns}) eq 'ARRAY' ) ?
+          @{ delete $attrs->{columns}} :
+            (delete $attrs->{columns} ||
+              $source->storage->order_columns_for_select(
+                $source,
+                [ $source->columns ]
+              )
+            );
   }
   # add the additional columns on
   foreach ( 'include_columns', '+columns' ) {
index 8a0b8cc..d321545 100644 (file)
@@ -641,7 +641,8 @@ sub disconnect {
 
     $self->_do_connection_actions(disconnect_call_ => $_) for @actions;
 
-    $self->_dbh->rollback unless $self->_dbh_autocommit;
+    $self->_dbh_rollback unless $self->_dbh_autocommit;
+
     $self->_dbh->disconnect;
     $self->_dbh(undef);
     $self->{_dbh_gen}++;
@@ -1053,27 +1054,31 @@ sub txn_begin {
   if($self->{transaction_depth} == 0) {
     $self->debugobj->txn_begin()
       if $self->debug;
-
-    # 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->_dbh_begin_work;
+  }
+  elsif ($self->auto_savepoint) {
     $self->svp_begin;
   }
   $self->{transaction_depth}++;
 }
 
+sub _dbh_begin_work {
+  my $self = shift;
+  # 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;
+}
+
 sub txn_commit {
   my $self = shift;
   if ($self->{transaction_depth} == 1) {
     my $dbh = $self->_dbh;
     $self->debugobj->txn_commit()
       if ($self->debug);
-    $dbh->commit;
+    $self->_dbh_commit;
     $self->{transaction_depth} = 0
       if $self->_dbh_autocommit;
   }
@@ -1084,6 +1089,11 @@ sub txn_commit {
   }
 }
 
+sub _dbh_commit {
+  my $self = shift;
+  $self->_dbh->commit;
+}
+
 sub txn_rollback {
   my $self = shift;
   my $dbh = $self->_dbh;
@@ -1093,7 +1103,7 @@ sub txn_rollback {
         if ($self->debug);
       $self->{transaction_depth} = 0
         if $self->_dbh_autocommit;
-      $dbh->rollback;
+      $self->_dbh_rollback;
     }
     elsif($self->{transaction_depth} > 1) {
       $self->{transaction_depth}--;
@@ -1116,6 +1126,11 @@ sub txn_rollback {
   }
 }
 
+sub _dbh_rollback {
+  my $self = shift;
+  $self->_dbh->rollback;
+}
+
 # This used to be the top-half of _execute.  It was split out to make it
 #  easier to override in NoBindVars without duping the rest.  It takes up
 #  all of _execute's args, and emits $sql, @bind.
@@ -2418,6 +2433,23 @@ sub lag_behind_master {
     return;
 }
 
+=head2 order_columns_for_select
+
+Returns an ordered list of column names for use with a C<SELECT> when the column
+list is not explicitly specified.
+By default returns the result of L<DBIx::Class::ResultSource/columns>.
+
+This may be overridden in a specific storage when there are requirements such
+as moving C<BLOB> columns to the end of the list.
+
+=cut
+
+sub order_columns_for_select {
+  my ($self, $source, $columns) = @_;
+
+  return @$columns;
+}
+
 sub DESTROY {
   my $self = shift;
   return if !$self->_dbh;
index 3d59e84..c4c9806 100644 (file)
@@ -3,7 +3,15 @@ package DBIx::Class::Storage::DBI::Cursor;
 use strict;
 use warnings;
 
-use base qw/DBIx::Class::Cursor/;
+use base qw/
+  Class::Accessor::Grouped
+  DBIx::Class::Cursor
+/;
+use mro 'c3';
+
+__PACKAGE__->mk_group_accessors('simple' =>
+    qw/sth/
+);
 
 =head1 NAME
 
@@ -73,24 +81,24 @@ sub _dbh_next {
       && $self->{attrs}{rows}
         && $self->{pos} >= $self->{attrs}{rows}
   ) {
-    $self->{sth}->finish if $self->{sth}->{Active};
-    delete $self->{sth};
+    $self->sth->finish if $self->sth->{Active};
+    $self->sth(undef);
     $self->{done} = 1;
   }
   return if $self->{done};
-  unless ($self->{sth}) {
-    $self->{sth} = ($storage->_select(@{$self->{args}}))[1];
+  unless ($self->sth) {
+    $self->sth(($storage->_select(@{$self->{args}}))[1]);
     if ($self->{attrs}{software_limit}) {
       if (my $offset = $self->{attrs}{offset}) {
-        $self->{sth}->fetch for 1 .. $offset;
+        $self->sth->fetch for 1 .. $offset;
       }
     }
   }
-  my @row = $self->{sth}->fetchrow_array;
+  my @row = $self->sth->fetchrow_array;
   if (@row) {
     $self->{pos}++;
   } else {
-    delete $self->{sth};
+    $self->sth(undef);
     $self->{done} = 1;
   }
   return @row;
@@ -120,8 +128,8 @@ sub _dbh_all {
   my ($storage, $dbh, $self) = @_;
 
   $self->_check_dbh_gen;
-  $self->{sth}->finish if $self->{sth}->{Active};
-  delete $self->{sth};
+  $self->sth->finish if $self->sth && $self->sth->{Active};
+  $self->sth(undef);
   my ($rv, $sth) = $storage->_select(@{$self->{args}});
   return @{$sth->fetchall_arrayref};
 }
@@ -146,14 +154,14 @@ sub reset {
   my ($self) = @_;
 
   # No need to care about failures here
-  eval { $self->{sth}->finish if $self->{sth} && $self->{sth}->{Active} };
+  eval { $self->sth->finish if $self->sth && $self->sth->{Active} };
   $self->_soft_reset;
 }
 
 sub _soft_reset {
   my ($self) = @_;
 
-  delete $self->{sth};
+  $self->sth(undef);
   delete $self->{done};
   $self->{pos} = 0;
   return $self;
@@ -173,7 +181,7 @@ sub DESTROY {
 
   # None of the reasons this would die matter if we're in DESTROY anyways
   local $@;
-  eval { $self->{sth}->finish if $self->{sth} && $self->{sth}->{Active} };
+  eval { $self->sth->finish if $self->sth && $self->sth->{Active} };
 }
 
 1;
index 95f1cac..126b9de 100644 (file)
@@ -40,7 +40,7 @@ Manually subs in the values for the usual C<?> placeholders.
 sub _prep_for_execute {
   my $self = shift;
 
-  my ($op, $extra_bind, $ident) = @_;
+  my ($op, $extra_bind, $ident, $args) = @_;
 
   my ($sql, $bind) = $self->next::method(@_);
 
@@ -49,15 +49,23 @@ sub _prep_for_execute {
   my @sql_part = split /\?/, $sql;
   my $new_sql;
 
+  my $col_info = $self->_resolve_column_info($ident, [ map $_->[0], @$bind ]);
+
   foreach my $bound (@$bind) {
     my $col = shift @$bound;
-    my $datatype = 'FIXME!!!';
+
+    my $datatype = $col_info->{$col}{data_type};
+
     foreach my $data (@$bound) {
-        if(ref $data) {
-            $data = ''.$data;
-        }
-        $data = $self->_dbh->quote($data);
-        $new_sql .= shift(@sql_part) . $data;
+      $data = ''.$data if ref $data;
+
+      $data = $self->transform_unbound_value($datatype, $data)
+        if $datatype;
+
+      $data = $self->_dbh->quote($data)
+        if (!$datatype || $self->should_quote_value($datatype, $data));
+
+      $new_sql .= shift(@sql_part) . $data;
     }
   }
   $new_sql .= join '', @sql_part;
@@ -65,6 +73,34 @@ sub _prep_for_execute {
   return ($new_sql, []);
 }
 
+=head2 should_quote_value
+
+This method is called by L</_prep_for_execute> for every column in
+order to determine if its value should be quoted or not. The arguments
+are the current column data type and the actual bind value. The return
+value is interpreted as: true - do quote, false - do not quote. You should
+override this in you Storage::DBI::<database> subclass, if your RDBMS
+does not like quotes around certain datatypes (e.g. Sybase and integer
+columns). The default method always returns true (do quote).
+
+ WARNING!!!                     
+
+ Always validate that the bind-value is valid for the current datatype.
+ Otherwise you may very well open the door to SQL injection attacks.
+
+=cut                            
+
+sub should_quote_value { 1 }
+
+=head2 transform_unbound_value
+
+Given a datatype and the value to be inserted directly into a SQL query, returns
+the necessary SQL fragment to represent that value.
+
+=cut
+
+sub transform_unbound_value { $_[2] }
+
 =head1 AUTHORS
 
 Brandon Black <blblack@gmail.com>
index 832da97..8b09f11 100644 (file)
@@ -206,12 +206,6 @@ sub connect_call_datetime_setup {
 "alter session set nls_timestamp_tz_format='$timestamp_tz_format'");
 }
 
-sub _svp_begin {
-    my ($self, $name) = @_;
-
-    $self->last_dbh->do("SAVEPOINT $name");
-}
-
 =head2 source_bind_attributes
 
 Handle LOB types in Oracle.  Under a certain size (4k?), you can get away
@@ -256,6 +250,12 @@ sub source_bind_attributes
        return \%bind_attributes;
 }
 
+sub _svp_begin {
+    my ($self, $name) = @_;
+
+    $self->last_dbh->do("SAVEPOINT $name");
+}
+
 # Oracle automatically releases a savepoint when you start another one with the
 # same name.
 sub _svp_release { 1 }
index c7031a1..7995334 100644 (file)
@@ -5,62 +5,642 @@ use warnings;
 
 use base qw/
     DBIx::Class::Storage::DBI::Sybase::Base
-    DBIx::Class::Storage::DBI::NoBindVars
 /;
 use mro 'c3';
+use Carp::Clan qw/^DBIx::Class/;
+use List::Util ();
+
+__PACKAGE__->mk_group_accessors('simple' =>
+    qw/_identity _blob_log_on_update auto_cast _insert_txn/
+);
+
+=head1 NAME
+
+DBIx::Class::Storage::DBI::Sybase - Sybase support for DBIx::Class
+
+=head1 SYNOPSIS
+
+This subclass supports L<DBD::Sybase> for real Sybase databases.  If you are
+using an MSSQL database via L<DBD::Sybase>, your storage will be reblessed to
+L<DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server>.
+
+=head1 DESCRIPTION
+
+If your version of Sybase does not support placeholders, then your storage
+will be reblessed to L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>. You can
+also enable that driver explicitly, see the documentation for more details.
+
+With this driver there is unfortunately no way to get the C<last_insert_id>
+without doing a C<SELECT MAX(col)>.
+
+But your queries will be cached.
+
+A recommended L<DBIx::Class::Storage::DBI/connect_info> setting:
+
+  on_connect_call => [['datetime_setup'], ['blob_setup', log_on_update => 0]]
+
+=head1 METHODS
+
+=cut
 
 sub _rebless {
-    my $self = shift;
+  my $self = shift;
 
+  if (ref($self) eq 'DBIx::Class::Storage::DBI::Sybase') {
     my $dbtype = eval {
-      @{$self->last_dbh
-        ->selectrow_arrayref(qq{sp_server_info \@attribute_id=1})
-      }[2]
-    };
-    unless ( $@ ) {
-        $dbtype =~ s/\W/_/gi;
-        my $subclass = "DBIx::Class::Storage::DBI::Sybase::${dbtype}";
-        if ($self->load_optional_class($subclass) && !$self->isa($subclass)) {
-            bless $self, $subclass;
+      @{$self->last_dbh->selectrow_arrayref(qq{sp_server_info \@attribute_id=1})}[2]
+    } || '';
+
+    my $exception = $@;
+    $dbtype =~ s/\W/_/gi;
+    my $subclass = "DBIx::Class::Storage::DBI::Sybase::${dbtype}";
+
+    if (!$exception && $dbtype && $self->load_optional_class($subclass)) {
+      bless $self, $subclass;
+      $self->_rebless;
+    } else { # real Sybase
+      my $no_bind_vars = 'DBIx::Class::Storage::DBI::Sybase::NoBindVars';
+
+# This is reset to 0 in ::NoBindVars, only necessary because we use max(col) to
+# get the identity.
+      $self->_insert_txn(1);
+
+      if ($self->using_freetds) {
+        carp <<'EOF' unless $ENV{DBIC_SYBASE_FREETDS_NOWARN};
+
+You are using FreeTDS with Sybase.
+
+We will do our best to support this configuration, but please consider this
+support experimental.
+
+TEXT/IMAGE columns will definitely not work.
+
+You are encouraged to recompile DBD::Sybase with the Sybase Open Client libraries
+instead.
+
+See perldoc DBIx::Class::Storage::DBI::Sybase for more details.
+
+To turn off this warning set the DBIC_SYBASE_FREETDS_NOWARN environment
+variable.
+EOF
+        if (not $self->placeholders_with_type_conversion_supported) {
+          if ($self->placeholders_supported) {
+            $self->auto_cast(1);
+          } else {
+            $self->ensure_class_loaded($no_bind_vars);
+            bless $self, $no_bind_vars;
             $self->_rebless;
+          }
         }
+
+        $self->set_textsize; # based on LongReadLen in connect_info
+
+      } elsif (not $self->dbh->{syb_dynamic_supported}) {
+# not necessarily FreeTDS, but no placeholders nevertheless
+        $self->ensure_class_loaded($no_bind_vars);
+        bless $self, $no_bind_vars;
+        $self->_rebless;
+      }
+      $self->_set_max_connect(256);
+    }
+  }
+}
+
+# Make sure we have CHAINED mode turned on if AutoCommit is off in non-FreeTDS
+# DBD::Sybase (since we don't know how DBD::Sybase was compiled.) If however
+# we're using FreeTDS, CHAINED mode turns on an implicit transaction which we
+# only want when AutoCommit is off.
+sub _populate_dbh {
+  my $self = shift;
+
+  $self->next::method(@_);
+
+  if (not $self->using_freetds) {
+    $self->_dbh->{syb_chained_txn} = 1;
+  } else {
+    if ($self->_dbh_autocommit) {
+      $self->_dbh->do('SET CHAINED OFF');
+    } else {
+      $self->_dbh->do('SET CHAINED ON');
+    }
+  }
+}
+
+=head2 connect_call_blob_setup
+
+Used as:
+
+  on_connect_call => [ [ 'blob_setup', log_on_update => 0 ] ]
+
+Does C<< $dbh->{syb_binary_images} = 1; >> to return C<IMAGE> data as raw binary
+instead of as a hex string.
+
+Recommended.
+
+Also sets the C<log_on_update> value for blob write operations. The default is
+C<1>, but C<0> is better if your database is configured for it.
+
+See
+L<DBD::Sybase/Handling_IMAGE/TEXT_data_with_syb_ct_get_data()/syb_ct_send_data()>.
+
+=cut
+
+sub connect_call_blob_setup {
+  my $self = shift;
+  my %args = @_;
+  my $dbh = $self->_dbh;
+  $dbh->{syb_binary_images} = 1;
+
+  $self->_blob_log_on_update($args{log_on_update})
+    if exists $args{log_on_update};
+}
+
+=head2 connect_call_set_auto_cast
+
+In some configurations (usually with L</FreeTDS>) statements with values bound
+to columns or conditions that are not strings will throw implicit type
+conversion errors. For L</FreeTDS> this is automatically detected, and this
+option is set.
+
+It converts placeholders to:
+
+  CAST(? as $type)
+
+the type is taken from the L<DBIx::Class::ResultSource/data_type> setting from
+your Result class, and mapped to a Sybase type using a mapping based on
+L<SQL::Translator> if necessary.
+
+This setting can also be set outside of
+L<DBIx::Class::Storage::DBI/connect_info> at any time using:
+
+  $schema->storage->auto_cast(1);
+
+=cut
+
+sub connect_call_set_auto_cast {
+  my $self = shift;
+  $self->auto_cast(1);
+}
+
+sub _is_lob_type {
+  my $self = shift;
+  my $type = shift;
+  $type && $type =~ /(?:text|image|lob|bytea|binary|memo)/i;
+}
+
+# The select-piggybacking-on-insert trick stolen from odbc/mssql
+sub _prep_for_execute {
+  my $self = shift;
+  my ($op, $extra_bind, $ident, $args) = @_;
+
+  my ($sql, $bind) = $self->next::method (@_);
+
+# Some combinations of FreeTDS and Sybase throw implicit conversion errors for
+# all placeeholders, so we convert them into CASTs here.
+# Based on code in ::DBI::NoBindVars .
+#
+# If we're using ::NoBindVars, there are no binds by this point so this code
+# gets skippeed.
+  if ($self->auto_cast && @$bind) {
+    my $new_sql;
+    my @sql_part = split /\?/, $sql;
+    my $col_info = $self->_resolve_column_info($ident,[ map $_->[0], @$bind ]);
+
+    foreach my $bound (@$bind) {
+      my $col = $bound->[0];
+      my $syb_type = $self->_syb_base_type($col_info->{$col}{data_type});
+
+      foreach my $data (@{$bound}[1..$#$bound]) {
+        $new_sql .= shift(@sql_part) .
+          ($syb_type ? "CAST(? AS $syb_type)" : '?');
+      }
+    }
+    $new_sql .= join '', @sql_part;
+    $sql = $new_sql;
+  }
+
+  if ($op eq 'insert') {
+    my $table = $ident->from;
+
+    my $bind_info = $self->_resolve_column_info(
+      $ident, [map $_->[0], @{$bind}]
+    );
+    my $identity_col =
+List::Util::first { $bind_info->{$_}{is_auto_increment} } (keys %$bind_info);
+
+    if ($identity_col) {
+      $sql =
+"SET IDENTITY_INSERT $table ON\n" .
+"$sql\n" .
+"SET IDENTITY_INSERT $table OFF"
+    } else {
+      $identity_col = List::Util::first {
+        $ident->column_info($_)->{is_auto_increment}
+      } $ident->columns;
     }
+
+    if ($identity_col) {
+      $sql =
+        "$sql\n" .
+        $self->_fetch_identity_sql($ident, $identity_col);
+    }
+  }
+
+  return ($sql, $bind);
+}
+
+# Stolen from SQLT, with some modifications. This will likely change when the
+# SQLT Sybase stuff is redone/fixed-up.
+my %TYPE_MAPPING  = (
+    number    => 'numeric',
+    money     => 'money',
+    varchar   => 'varchar',
+    varchar2  => 'varchar',
+    timestamp => 'datetime',
+    text      => 'varchar',
+    real      => 'double precision',
+    comment   => 'text',
+    bit       => 'bit',
+    tinyint   => 'smallint',
+    float     => 'double precision',
+    serial    => 'numeric',
+    bigserial => 'numeric',
+    boolean   => 'varchar',
+    long      => 'varchar',
+);
+
+sub _syb_base_type {
+  my ($self, $type) = @_;
+
+  $type = lc $type;
+  $type =~ s/ identity//;
+
+  return uc($TYPE_MAPPING{$type} || $type);
 }
 
-sub _dbh_last_insert_id {
-    my ($self, $dbh, $source, $col) = @_;
-    return ($dbh->selectrow_array('select @@identity'))[0];
+sub _fetch_identity_sql {
+  my ($self, $source, $col) = @_;
+
+  return "SELECT MAX($col) FROM ".$source->from;
+}
+
+sub _execute {
+  my $self = shift;
+  my ($op) = @_;
+
+  my ($rv, $sth, @bind) = $self->dbh_do($self->can('_dbh_execute'), @_);
+
+  if ($op eq 'insert') {
+    $self->_identity($sth->fetchrow_array);
+    $sth->finish;
+  }
+
+  return wantarray ? ($rv, $sth, @bind) : $rv;
+}
+
+sub last_insert_id { shift->_identity }
+
+# override to handle TEXT/IMAGE and to do a transaction if necessary
+sub insert {
+  my ($self, $source, $to_insert) = splice @_, 0, 3;
+  my $dbh = $self->_dbh;
+
+  my $blob_cols = $self->_remove_blob_cols($source, $to_insert);
+
+# We have to do the insert in a transaction to avoid race conditions with the
+# SELECT MAX(COL) identity method used when placeholders are enabled.
+  my $updated_cols = do {
+    if ($self->_insert_txn && (not $self->{transaction_depth})) {
+      my $args = \@_;
+      my $method = $self->next::can;
+      $self->txn_do(
+        sub { $self->$method($source, $to_insert, @$args) }
+      );
+    } else {
+      $self->next::method($source, $to_insert, @_);
+    }
+  };
+
+  $self->_insert_blobs($source, $blob_cols, $to_insert) if %$blob_cols;
+
+  return $updated_cols;
+}
+
+sub update {
+  my ($self, $source)  = splice @_, 0, 2;
+  my ($fields, $where) = @_;
+  my $wantarray        = wantarray;
+
+  my $blob_cols = $self->_remove_blob_cols($source, $fields);
+
+  my @res;
+  if ($wantarray) {
+    @res    = $self->next::method($source, @_);
+  } else {
+    $res[0] = $self->next::method($source, @_);
+  }
+
+  $self->_update_blobs($source, $blob_cols, $where) if %$blob_cols;
+
+  return $wantarray ? @res : $res[0];
+}
+
+sub _remove_blob_cols {
+  my ($self, $source, $fields) = @_;
+
+  my %blob_cols;
+
+  for my $col (keys %$fields) {
+    if ($self->_is_lob_type($source->column_info($col)->{data_type})) {
+      $blob_cols{$col} = delete $fields->{$col};
+      $fields->{$col} = \"''";
+    }
+  }
+
+  return \%blob_cols;
+}
+
+sub _update_blobs {
+  my ($self, $source, $blob_cols, $where) = @_;
+
+  my (@primary_cols) = $source->primary_columns;
+
+  croak "Cannot update TEXT/IMAGE column(s) without a primary key"
+    unless @primary_cols;
+
+# check if we're updating a single row by PK
+  my $pk_cols_in_where = 0;
+  for my $col (@primary_cols) {
+    $pk_cols_in_where++ if defined $where->{$col};
+  }
+  my @rows;
+
+  if ($pk_cols_in_where == @primary_cols) {
+    my %row_to_update;
+    @row_to_update{@primary_cols} = @{$where}{@primary_cols};
+    @rows = \%row_to_update;
+  } else {
+    my $rs = $source->resultset->search(
+      $where,
+      {
+        result_class => 'DBIx::Class::ResultClass::HashRefInflator',
+        select => \@primary_cols
+      }
+    );
+    @rows = $rs->all; # statement must finish
+  }
+
+  for my $row (@rows) {
+    $self->_insert_blobs($source, $blob_cols, $row);
+  }
+}
+
+sub _insert_blobs {
+  my ($self, $source, $blob_cols, $row) = @_;
+  my $dbh = $self->dbh;
+
+  my $table = $source->from;
+
+  my %row = %$row;
+  my (@primary_cols) = $source->primary_columns;
+
+  croak "Cannot update TEXT/IMAGE column(s) without a primary key"
+    unless @primary_cols;
+
+  if ((grep { defined $row{$_} } @primary_cols) != @primary_cols) {
+    if (@primary_cols == 1) {
+      my $col = $primary_cols[0];
+      $row{$col} = $self->last_insert_id($source, $col);
+    } else {
+      croak "Cannot update TEXT/IMAGE column(s) without primary key values";
+    }
+  }
+
+  for my $col (keys %$blob_cols) {
+    my $blob = $blob_cols->{$col};
+
+    my %where = map { ($_, $row{$_}) } @primary_cols;
+    my $cursor = $source->resultset->search(\%where, {
+      select => [$col]
+    })->cursor;
+    $cursor->next;
+    my $sth = $cursor->sth;
+
+    eval {
+      do {
+        $sth->func('CS_GET', 1, 'ct_data_info') or die $sth->errstr;
+      } while $sth->fetch;
+
+      $sth->func('ct_prepare_send') or die $sth->errstr;
+
+      my $log_on_update = $self->_blob_log_on_update;
+      $log_on_update    = 1 if not defined $log_on_update;
+
+      $sth->func('CS_SET', 1, {
+        total_txtlen => length($blob),
+        log_on_update => $log_on_update
+      }, 'ct_data_info') or die $sth->errstr;
+
+      $sth->func($blob, length($blob), 'ct_send_data') or die $sth->errstr;
+
+      $sth->func('ct_finish_send') or die $sth->errstr;
+    };
+    my $exception = $@;
+    $sth->finish if $sth;
+    if ($exception) {
+      if ($self->using_freetds) {
+        croak
+"TEXT/IMAGE operation failed, probably because you're using FreeTDS: " .
+$exception;
+      } else {
+        croak $exception;
+      }
+    }
+  }
+}
+
+=head2 connect_call_datetime_setup
+
+Used as:
+
+  on_connect_call => 'datetime_setup'
+
+In L<DBIx::Class::Storage::DBI/connect_info> to set:
+
+  $dbh->syb_date_fmt('ISO_strict'); # output fmt: 2004-08-21T14:36:48.080Z
+  $dbh->do('set dateformat mdy');   # input fmt:  08/13/1979 18:08:55.080
+
+On connection for use with L<DBIx::Class::InflateColumn::DateTime>, using
+L<DateTime::Format::Sybase>, which you will need to install.
+
+This works for both C<DATETIME> and C<SMALLDATETIME> columns, although
+C<SMALLDATETIME> columns only have minute precision.
+
+=cut
+
+{
+  my $old_dbd_warned = 0;
+
+  sub connect_call_datetime_setup {
+    my $self = shift;
+    my $dbh = $self->_dbh;
+
+    if ($dbh->can('syb_date_fmt')) {
+# amazingly, this works with FreeTDS
+      $dbh->syb_date_fmt('ISO_strict');
+    } elsif (not $old_dbd_warned) {
+      carp "Your DBD::Sybase is too old to support ".
+      "DBIx::Class::InflateColumn::DateTime, please upgrade!";
+      $old_dbd_warned = 1;
+    }
+
+    $dbh->do('SET DATEFORMAT mdy');
+
+    1;
+  }
+}
+
+sub datetime_parser_type { "DateTime::Format::Sybase" }
+
+# ->begin_work and such have no effect with FreeTDS but we run them anyway to
+# let the DBD keep any state it needs to.
+#
+# If they ever do start working, the extra statements will do no harm (because
+# Sybase supports nested transactions.)
+
+sub _dbh_begin_work {
+  my $self = shift;
+  $self->next::method(@_);
+  if ($self->using_freetds) {
+    $self->dbh->do('BEGIN TRAN');
+  }
+}
+
+sub _dbh_commit {
+  my $self = shift;
+  if ($self->using_freetds) {
+    $self->_dbh->do('COMMIT');
+  }
+  return $self->next::method(@_);
+}
+
+sub _dbh_rollback {
+  my $self = shift;
+  if ($self->using_freetds) {
+    $self->_dbh->do('ROLLBACK');
+  }
+  return $self->next::method(@_);
+}
+
+# savepoint support using ASE syntax
+
+sub _svp_begin {
+  my ($self, $name) = @_;
+
+  $self->dbh->do("SAVE TRANSACTION $name");
+}
+
+# A new SAVE TRANSACTION with the same name releases the previous one.
+sub _svp_release { 1 }
+
+sub _svp_rollback {
+  my ($self, $name) = @_;
+
+  $self->dbh->do("ROLLBACK TRANSACTION $name");
 }
 
 1;
 
-=head1 NAME
+=head1 Schema::Loader Support
 
-DBIx::Class::Storage::DBI::Sybase - Storage::DBI subclass for Sybase
+There is an experimental branch of L<DBIx::Class::Schema::Loader> that will
+allow you to dump a schema from most (if not all) versions of Sybase.
 
-=head1 SYNOPSIS
+It is available via subversion from:
 
-This subclass supports L<DBD::Sybase> for real Sybase databases.  If
-you are using an MSSQL database via L<DBD::Sybase>, see
-L<DBIx::Class::Storage::DBI::Sybase::MSSQL>.
+  http://dev.catalyst.perl.org/repos/bast/branches/DBIx-Class-Schema-Loader/mssql_tweaks
 
-=head1 CAVEATS
+=head1 FreeTDS
 
-This storage driver uses L<DBIx::Class::Storage::DBI::NoBindVars> as a base.
-This means that bind variables will be interpolated (properly quoted of course)
-into the SQL query itself, without using bind placeholders.
+This driver supports L<DBD::Sybase> compiled against FreeTDS
+(L<http://www.freetds.org/>) to the best of our ability, however it is
+recommended that you recompile L<DBD::Sybase> against the Sybase Open Client
+libraries. They are a part of the Sybase ASE distribution:
 
-More importantly this means that caching of prepared statements is explicitly
-disabled, as the interpolation renders it useless.
+The Open Client FAQ is here:
+L<http://www.isug.com/Sybase_FAQ/ASE/section7.html>.
 
-=head1 AUTHORS
+Sybase ASE for Linux (which comes with the Open Client libraries) may be
+downloaded here: L<http://response.sybase.com/forms/ASE_Linux_Download>.
+
+To see if you're using FreeTDS check C<< $schema->storage->using_freetds >>, or run:
+
+  perl -MDBI -le 'my $dbh = DBI->connect($dsn, $user, $pass); print $dbh->{syb_oc_version}'
+
+Some versions of the libraries involved will not support placeholders, in which
+case the storage will be reblessed to
+L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>.
+
+In some configurations, placeholders will work but will throw implicit
+conversion errors for anything that's not expecting a string. In such a case,
+the C<auto_cast> option is automatically set, which you may enable yourself with
+L</connect_call_set_auto_cast> (see the description of that method for more
+details.)
+
+In other configurations, placeholers will work just as they do with the Sybase
+Open Client libraries.
+
+Inserts or updates of TEXT/IMAGE columns will B<NOT> work with FreeTDS.
+
+=head1 MAXIMUM CONNECTIONS
+
+The TDS protocol makes separate connections to the server for active statements
+in the background. By default the number of such connections is limited to 25,
+on both the client side and the server side.
+
+This is a bit too low for a complex L<DBIx::Class> application, so on connection
+the client side setting is set to C<256> (see L<DBD::Sybase/maxConnect>.) You
+can override it to whatever setting you like in the DSN.
+
+See
+L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
+for information on changing the setting on the server side.
+
+=head1 DATES
 
-Brandon L Black <blblack@gmail.com>
+See L</connect_call_datetime_setup> to setup date formats
+for L<DBIx::Class::InflateColumn::DateTime>.
+
+=head1 TEXT/IMAGE COLUMNS
+
+L<DBD::Sybase> compiled with FreeTDS will B<NOT> allow you to insert or update
+C<TEXT/IMAGE> columns.
+
+Setting C<< $dbh->{LongReadLen} >> will also not work with FreeTDS use either:
+
+  $schema->storage->dbh->do("SET TEXTSIZE $bytes");
+
+or
+
+  $schema->storage->set_textsize($bytes);
+
+instead.
+
+However, the C<LongReadLen> you pass in
+L<DBIx::Class::Storage::DBI/connect_info> is used to execute the equivalent
+C<SET TEXTSIZE> command on connection.
+
+See L</connect_call_blob_setup> for a L<DBIx::Class::Storage::DBI/connect_info>
+setting you need to work with C<IMAGE> columns.
+
+=head1 AUTHORS
 
-Justin Hunter <justin.d.hunter@gmail.com>
+See L<DBIx::Class/CONTRIBUTORS>.
 
 =head1 LICENSE
 
 You may distribute this code under the same terms as Perl itself.
 
 =cut
+# vim:sts=2 sw=2:
index b375a8c..2284289 100644 (file)
@@ -1,5 +1,4 @@
-package # hide from PAUSE
-    DBIx::Class::Storage::DBI::Sybase::Base;
+package DBIx::Class::Storage::DBI::Sybase::Base;
 
 use strict;
 use warnings;
@@ -12,6 +11,15 @@ use mro 'c3';
 DBIx::Class::Storage::DBI::Sybase::Base - Common functionality for drivers using
 DBD::Sybase
 
+=head1 DESCRIPTION
+
+This is the base class for L<DBIx::Class::Storage::DBI::Sybase> and
+L<DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server>. It provides some
+utility methods related to L<DBD::Sybase> and the supported functions of the
+database you are connecting to.
+
+=head1 METHODS
+
 =cut
 
 sub _ping {
@@ -27,7 +35,14 @@ sub _ping {
   return $@ ? 0 : 1;
 }
 
-sub _placeholders_supported {
+=head2 placeholders_supported
+
+Whether or not string placeholders work. Does not check for implicit conversion
+errors, see L</placeholders_with_type_conversion_supported>.
+
+=cut
+
+sub placeholders_supported {
   my $self = shift;
   my $dbh  = $self->last_dbh;
 
@@ -36,11 +51,77 @@ sub _placeholders_supported {
 # purpose.
     local $dbh->{PrintError} = 0;
     local $dbh->{RaiseError} = 1;
+    $dbh->selectrow_array('select ?', {}, 1);
+  };
+}
+
+=head2 placeholders_with_type_conversion_supported 
+
+=cut
+
+sub placeholders_with_type_conversion_supported {
+  my $self = shift;
+  my $dbh  = $self->_dbh;
+
+  return eval {
+    local $dbh->{PrintError} = 0;
+    local $dbh->{RaiseError} = 1;
 # this specifically tests a bind that is NOT a string
     $dbh->selectrow_array('select 1 where 1 = ?', {}, 1);
   };
 }
 
+sub _set_max_connect {
+  my $self = shift;
+  my $val  = shift || 256;
+
+  my $dsn = $self->_dbi_connect_info->[0];
+
+  return if ref($dsn) eq 'CODE';
+
+  if ($dsn !~ /maxConnect=/) {
+    $self->_dbi_connect_info->[0] = "$dsn;maxConnect=$val";
+    my $connected = defined $self->_dbh;
+    $self->disconnect;
+    $self->ensure_connected if $connected;
+  }
+}
+
+=head2 using_freetds
+
+Whether or not L<DBD::Sybase> was compiled against FreeTDS. If false, it means
+the Sybase OpenClient libraries were used.
+
+=cut
+
+sub using_freetds {
+  my $self = shift;
+
+  return $self->_dbh->{syb_oc_version} =~ /freetds/i;
+}
+
+=head2 set_textsize
+
+When using FreeTDS and/or MSSQL, C<< $dbh->{LongReadLen} >> is not available,
+use this function instead. It does:
+
+  $dbh->do("SET TEXTSIZE $bytes");
+
+Takes the number of bytes, or uses the C<LongReadLen> value from your
+L<DBIx::Class/connect_info> if omitted.
+
+=cut
+
+sub set_textsize {
+  my $self = shift;
+  my $text_size = shift ||
+    eval { $self->_dbi_connect_info->[-1]->{LongReadLen} };
+
+  return unless defined $text_size;
+
+  $self->_dbh->do("SET TEXTSIZE $text_size");
+}
+
 1;
 
 =head1 AUTHORS
index 6cda222..5c3fb4c 100644 (file)
@@ -13,7 +13,7 @@ sub _rebless {
   my $self = shift;
   my $dbh  = $self->last_dbh;
 
-  if (not $self->_placeholders_supported) {
+  if (not $self->placeholders_with_type_conversion_supported) {
     bless $self,
       'DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server::NoBindVars';
     $self->_rebless;
diff --git a/lib/DBIx/Class/Storage/DBI/Sybase/NoBindVars.pm b/lib/DBIx/Class/Storage/DBI/Sybase/NoBindVars.pm
new file mode 100644 (file)
index 0000000..78bf807
--- /dev/null
@@ -0,0 +1,106 @@
+package DBIx::Class::Storage::DBI::Sybase::NoBindVars;
+
+use Class::C3;
+use base qw/
+  DBIx::Class::Storage::DBI::NoBindVars
+  DBIx::Class::Storage::DBI::Sybase
+/;
+use List::Util ();
+use Scalar::Util ();
+
+sub _rebless {
+  my $self = shift;
+  $self->disable_sth_caching(1);
+  $self->_insert_txn(0);
+}
+
+# this works when NOT using placeholders
+sub _fetch_identity_sql { 'SELECT @@IDENTITY' }
+
+my $number = sub { Scalar::Util::looks_like_number($_[0]) };
+
+my $decimal = sub { $_[0] =~ /^ [-+]? \d+ (?:\.\d*)? \z/x };
+
+my %noquote = (
+    int => sub { $_[0] =~ /^ [-+]? \d+ \z/x },
+    bit => => sub { $_[0] =~ /^[01]\z/ },
+    money => sub { $_[0] =~ /^\$ \d+ (?:\.\d*)? \z/x },
+    float => $number,
+    real => $number,
+    double => $number,
+    decimal => $decimal,
+    numeric => $decimal,
+);
+
+sub should_quote_value {
+  my $self = shift;
+  my ($type, $value) = @_;
+
+  return $self->next::method(@_) if not defined $value or not defined $type;
+
+  if (my $key = List::Util::first { $type =~ /$_/i } keys %noquote) {
+    return 0 if $noquote{$key}->($value);
+  } elsif ($self->is_datatype_numeric($type) && $number->($value)) {
+    return 0;
+  }
+
+## try to guess based on value
+#  elsif (not $type) {
+#    return 0 if $number->($value) || $noquote->{money}->($value);
+#  }
+
+  return $self->next::method(@_);
+}
+
+sub transform_unbound_value {
+  my ($self, $type, $value) = @_;
+
+  if ($type =~ /money/i && defined $value) {
+    $value =~ s/^\$//;
+    $value = '$' . $value;
+  }
+
+  return $value;
+}
+
+1;
+
+=head1 NAME
+
+DBIx::Class::Storage::DBI::Sybase::NoBindVars - Storage::DBI subclass for Sybase
+without placeholder support
+
+=head1 DESCRIPTION
+
+If you're using this driver than your version of Sybase, or the libraries you
+use to connect to it, do not support placeholders.
+
+You can also enable this driver explicitly using:
+
+  my $schema = SchemaClass->clone;
+  $schema->storage_type('::DBI::Sybase::NoBindVars');
+  $schema->connect($dsn, $user, $pass, \%opts);
+
+See the discussion in L<< DBD::Sybase/Using ? Placeholders & bind parameters to
+$sth->execute >> for details on the pros and cons of using placeholders.
+
+One advantage of not using placeholders is that C<select @@identity> will work
+for obtainging the last insert id of an C<IDENTITY> column, instead of having to
+do C<select max(col)> in a transaction as the base Sybase driver does.
+
+When using this driver, bind variables will be interpolated (properly quoted of
+course) into the SQL query itself, without using placeholders.
+
+The caching of prepared statements is also explicitly disabled, as the
+interpolation renders it useless.
+
+=head1 AUTHORS
+
+See L<DBIx::Class/CONTRIBUTORS>.
+
+=head1 LICENSE
+
+You may distribute this code under the same terms as Perl itself.
+
+=cut
+# vim:sts=2 sw=2:
index 40a6157..be4002e 100644 (file)
@@ -145,14 +145,11 @@ $schema->storage->dbh_do (sub {
     my ($storage, $dbh) = @_;
     eval { $dbh->do("DROP TABLE money_test") };
     $dbh->do(<<'SQL');
-
 CREATE TABLE money_test (
    id INT IDENTITY PRIMARY KEY,
    amount MONEY NULL
 )
-
 SQL
-
 });
 
 my $rs = $schema->resultset('Money');
index 9fc87f0..9f52894 100644 (file)
@@ -1,5 +1,6 @@
 use strict;
 use warnings;  
+no warnings 'uninitialized';
 
 use Test::More;
 use Test::Exception;
@@ -8,84 +9,267 @@ use DBICTest;
 
 my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_SYBASE_${_}" } qw/DSN USER PASS/};
 
-plan skip_all => 'Set $ENV{DBICTEST_SYBASE_DSN}, _USER and _PASS to run this test'
-  unless ($dsn && $user);
+my $TESTS = 35 + 2;
 
-plan tests => 13;
+if (not ($dsn && $user)) {
+  plan skip_all =>
+    'Set $ENV{DBICTEST_SYBASE_DSN}, _USER and _PASS to run this test' .
+    "\nWarning: This test drops and creates the tables " .
+    "'artist' and 'bindtype_test'";
+} else {
+  plan tests => $TESTS*2;
+}
+
+my @storage_types = (
+  'DBI::Sybase',
+  'DBI::Sybase::NoBindVars',
+);
+my $schema;
+my $storage_idx = -1;
+
+for my $storage_type (@storage_types) {
+  $storage_idx++;
+# this is so we can set ->storage_type before connecting
+  my $schema = DBICTest::Schema->clone;
 
-my $schema = DBICTest::Schema->connect($dsn, $user, $pass, {AutoCommit => 1});
+  unless ($storage_type eq 'DBI::Sybase') { # autodetect
+    $schema->storage_type("::$storage_type");
+  }
 
-# start disconnected to test reconnection
-$schema->storage->ensure_connected;
-$schema->storage->_dbh->disconnect;
+  $schema->connection($dsn, $user, $pass, {
+    AutoCommit => 1,
+    on_connect_call => [
+      [ blob_setup => log_on_update => 1 ], # this is a safer option
+    ],
+  });
 
-isa_ok( $schema->storage, 'DBIx::Class::Storage::DBI::Sybase' );
+  $schema->storage->ensure_connected;
 
-my $dbh;
-lives_ok (sub {
-  $dbh = $schema->storage->dbh;
-}, 'reconnect works');
+  if ($storage_idx == 0 &&
+      $schema->storage->isa('DBIx::Class::Storage::DBI::Sybase::NoBindVars')) {
+# no placeholders in this version of Sybase or DBD::Sybase (or using FreeTDS)
+      my $tb = Test::More->builder;
+      $tb->skip('no placeholders') for 1..$TESTS;
+      next;
+  }
 
-$schema->storage->dbh_do (sub {
-    my ($storage, $dbh) = @_;
-    eval { $dbh->do("DROP TABLE artist") };
-    $dbh->do(<<'SQL');
+  isa_ok( $schema->storage, "DBIx::Class::Storage::$storage_type" );
 
+  $schema->storage->_dbh->disconnect;
+  lives_ok (sub { $schema->storage->dbh }, 'reconnect works');
+
+  $schema->storage->dbh_do (sub {
+      my ($storage, $dbh) = @_;
+      eval { $dbh->do("DROP TABLE artist") };
+      $dbh->do(<<'SQL');
 CREATE TABLE artist (
-   artistid INT IDENTITY NOT NULL,
+   artistid INT IDENTITY PRIMARY KEY,
    name VARCHAR(100),
    rank INT DEFAULT 13 NOT NULL,
-   charfield CHAR(10) NULL,
-   primary key(artistid)
+   charfield CHAR(10) NULL
 )
-
 SQL
+  });
 
-});
+  my %seen_id;
 
-my %seen_id;
+# so we start unconnected
+  $schema->storage->disconnect;
 
-# fresh $schema so we start unconnected
-$schema = DBICTest::Schema->connect($dsn, $user, $pass, {AutoCommit => 1});
+# inserts happen in a txn, so we make sure it still works inside a txn too
+  $schema->txn_begin;
 
 # test primary key handling
-my $new = $schema->resultset('Artist')->create({ name => 'foo' });
-ok($new->artistid > 0, "Auto-PK worked");
+  my $new = $schema->resultset('Artist')->create({ name => 'foo' });
+  ok($new->artistid > 0, "Auto-PK worked");
 
-$seen_id{$new->artistid}++;
+  $seen_id{$new->artistid}++;
 
-# test LIMIT support
-for (1..6) {
+  for (1..6) {
     $new = $schema->resultset('Artist')->create({ name => 'Artist ' . $_ });
     is ( $seen_id{$new->artistid}, undef, "id for Artist $_ is unique" );
     $seen_id{$new->artistid}++;
-}
+  }
+
+  $schema->txn_commit;
 
-my $it;
+# test simple count
+  is ($schema->resultset('Artist')->count, 7, 'count(*) of whole table ok');
 
-$it = $schema->resultset('Artist')->search( {}, {
+# test LIMIT support
+  my $it = $schema->resultset('Artist')->search({
+    artistid => { '>' => 0 }
+  }, {
     rows => 3,
     order_by => 'artistid',
-});
+  });
 
-TODO: {
-    local $TODO = 'Sybase is very very fucked in the limit department';
+  is( $it->count, 3, "LIMIT count ok" );
 
-    is( $it->count, 3, "LIMIT count ok" );
-}
+  is( $it->next->name, "foo", "iterator->next ok" );
+  $it->next;
+  is( $it->next->name, "Artist 2", "iterator->next ok" );
+  is( $it->next, undef, "next past end of resultset ok" );
+
+# now try with offset
+  $it = $schema->resultset('Artist')->search({}, {
+    rows => 3,
+    offset => 3,
+    order_by => 'artistid',
+  });
+
+  is( $it->count, 3, "LIMIT with offset count ok" );
+
+  is( $it->next->name, "Artist 3", "iterator->next ok" );
+  $it->next;
+  is( $it->next->name, "Artist 5", "iterator->next ok" );
+  is( $it->next, undef, "next past end of resultset ok" );
+
+# now try a grouped count
+  $schema->resultset('Artist')->create({ name => 'Artist 6' })
+    for (1..6);
+
+  $it = $schema->resultset('Artist')->search({}, {
+    group_by => 'name'
+  });
+
+  is( $it->count, 7, 'COUNT of GROUP_BY ok' );
+
+# mostly stolen from the blob stuff Nniuq wrote for t/73oracle.t
+  SKIP: {
+    skip 'TEXT/IMAGE support does not work with FreeTDS', 12
+      if $schema->storage->using_freetds;
+
+    my $dbh = $schema->storage->dbh;
+    {
+      local $SIG{__WARN__} = sub {};
+      eval { $dbh->do('DROP TABLE bindtype_test') };
+
+      $dbh->do(qq[
+        CREATE TABLE bindtype_test 
+        (
+          id    INT   IDENTITY PRIMARY KEY,
+          bytea INT   NULL,
+          blob  IMAGE NULL,
+          clob  TEXT  NULL
+        )
+      ],{ RaiseError => 1, PrintError => 0 });
+    }
 
-# The iterator still works correctly with rows => 3, even though the sql is
-# fucked, very interesting.
+    my %binstr = ( 'small' => join('', map { chr($_) } ( 1 .. 127 )) );
+    $binstr{'large'} = $binstr{'small'} x 1024;
 
-is( $it->next->name, "foo", "iterator->next ok" );
-$it->next;
-is( $it->next->name, "Artist 2", "iterator->next ok" );
-is( $it->next, undef, "next past end of resultset ok" );
+    my $maxloblen = length $binstr{'large'};
+    
+    if (not $schema->storage->using_freetds) {
+      $dbh->{'LongReadLen'} = $maxloblen * 2;
+    } else {
+      $dbh->do("set textsize ".($maxloblen * 2));
+    }
 
+    my $rs = $schema->resultset('BindType');
+    my $last_id;
+
+    foreach my $type (qw(blob clob)) {
+      foreach my $size (qw(small large)) {
+        no warnings 'uninitialized';
+
+        my $created = eval { $rs->create( { $type => $binstr{$size} } ) };
+        ok(!$@, "inserted $size $type without dying");
+        diag $@ if $@;
+
+        $last_id = $created->id if $created;
+
+        my $got = eval {
+          $rs->find($last_id)->$type
+        };
+        diag $@ if $@;
+        ok($got eq $binstr{$size}, "verified inserted $size $type");
+      }
+    }
+
+    # blob insert with explicit PK
+    # also a good opportunity to test IDENTITY_INSERT
+    {
+      local $SIG{__WARN__} = sub {};
+      eval { $dbh->do('DROP TABLE bindtype_test') };
+
+      $dbh->do(qq[
+        CREATE TABLE bindtype_test 
+        (
+          id    INT   IDENTITY PRIMARY KEY,
+          bytea INT   NULL,
+          blob  IMAGE NULL,
+          clob  TEXT  NULL
+        )
+      ],{ RaiseError => 1, PrintError => 0 });
+    }
+    my $created = eval { $rs->create( { id => 1, blob => $binstr{large} } ) };
+    ok(!$@, "inserted large blob without dying with manual PK");
+    diag $@ if $@;
+
+    my $got = eval {
+      $rs->find(1)->blob
+    };
+    diag $@ if $@;
+    ok($got eq $binstr{large}, "verified inserted large blob with manual PK");
+
+    # try a blob update
+    my $new_str = $binstr{large} . 'mtfnpy';
+    eval { $rs->search({ id => 1 })->update({ blob => $new_str }) };
+    ok !$@, 'updated blob successfully';
+    diag $@ if $@;
+    $got = eval {
+      $rs->find(1)->blob
+    };
+    diag $@ if $@;
+    ok($got eq $new_str, "verified updated blob");
+  }
+
+# test MONEY column support
+  $schema->storage->dbh_do (sub {
+      my ($storage, $dbh) = @_;
+      eval { $dbh->do("DROP TABLE money_test") };
+      $dbh->do(<<'SQL');
+CREATE TABLE money_test (
+   id INT IDENTITY PRIMARY KEY,
+   amount MONEY NULL
+)
+SQL
+  });
+
+  my $rs = $schema->resultset('Money');
+
+  my $row;
+  lives_ok {
+    $row = $rs->create({ amount => 100 });
+  } 'inserted a money value';
+
+  is eval { $rs->find($row->id)->amount }, 100, 'money value round-trip';
+
+  lives_ok {
+    $row->update({ amount => 200 });
+  } 'updated a money value';
+
+  is eval { $rs->find($row->id)->amount },
+    200, 'updated money value round-trip';
+
+  lives_ok {
+    $row->update({ amount => undef });
+  } 'updated a money value to NULL';
+
+  my $null_amount = eval { $rs->find($row->id)->amount };
+  ok(
+    (($null_amount == undef) && (not $@)),
+    'updated money value to NULL round-trip'
+  );
+  diag $@ if $@;
+}
 
 # clean up our mess
 END {
-    my $dbh = eval { $schema->storage->_dbh };
-    $dbh->do('DROP TABLE artist') if $dbh;
+  if (my $dbh = eval { $schema->storage->_dbh }) {
+    eval { $dbh->do("DROP TABLE $_") }
+      for qw/artist bindtype_test money_test/;
+  }
 }
-
diff --git a/t/inflate/datetime_sybase.t b/t/inflate/datetime_sybase.t
new file mode 100644 (file)
index 0000000..24d0f07
--- /dev/null
@@ -0,0 +1,85 @@
+use strict;
+use warnings;  
+
+use Test::More;
+use Test::Exception;
+use lib qw(t/lib);
+use DBICTest;
+
+my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_SYBASE_${_}" } qw/DSN USER PASS/};
+
+if (not ($dsn && $user)) {
+  plan skip_all =>
+    'Set $ENV{DBICTEST_SYBASE_DSN}, _USER and _PASS to run this test' .
+    "\nWarning: This test drops and creates a table called 'track'";
+} else {
+  eval "use DateTime; use DateTime::Format::Sybase;";
+  if ($@) {
+    plan skip_all => 'needs DateTime and DateTime::Format::Sybase for testing';
+  }
+  else {
+    plan tests => (4 * 2 * 2) + 2; # (tests * dt_types * storage_types) + storage_tests
+  }
+}
+
+my @storage_types = (
+  'DBI::Sybase',
+  'DBI::Sybase::NoBindVars',
+);
+my $schema;
+
+for my $storage_type (@storage_types) {
+  $schema = DBICTest::Schema->clone;
+
+  unless ($storage_type eq 'DBI::Sybase') { # autodetect
+    $schema->storage_type("::$storage_type");
+  }
+  $schema->connection($dsn, $user, $pass, {
+    AutoCommit => 1,
+    on_connect_call => [ 'datetime_setup' ],
+  });
+
+  $schema->storage->ensure_connected;
+
+  isa_ok( $schema->storage, "DBIx::Class::Storage::$storage_type" );
+
+# coltype, col, date
+  my @dt_types = (
+    ['DATETIME', 'last_updated_at', '2004-08-21T14:36:48.080Z'],
+# minute precision
+    ['SMALLDATETIME', 'small_dt', '2004-08-21T14:36:00.000Z'],
+  );
+  
+  for my $dt_type (@dt_types) {
+    my ($type, $col, $sample_dt) = @$dt_type;
+
+    eval { $schema->storage->dbh->do("DROP TABLE track") };
+    $schema->storage->dbh->do(<<"SQL");
+CREATE TABLE track (
+   trackid INT IDENTITY PRIMARY KEY,
+   cd INT,
+   position INT,
+   $col $type,
+)
+SQL
+    ok(my $dt = DateTime::Format::Sybase->parse_datetime($sample_dt));
+
+    my $row;
+    ok( $row = $schema->resultset('Track')->create({
+          $col => $dt,
+          cd => 1,
+        }));
+    ok( $row = $schema->resultset('Track')
+      ->search({ trackid => $row->trackid }, { select => [$col] })
+      ->first
+    );
+    is( $row->$col, $dt, 'DateTime roundtrip' );
+  }
+}
+
+# clean up our mess
+END {
+  if (my $dbh = eval { $schema->storage->_dbh }) {
+    $dbh->do('DROP TABLE track');
+  }
+}
index 20b8e5a..1c05cec 100644 (file)
@@ -1,5 +1,3 @@
--- 
--- Created by SQL::Translator::Producer::SQLite
 -- Created on Thu Jul 30 09:37:43 2009
 --