1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313
|
-- list not complete
-- to be found DBM
-- to be found ifNull
-- to be found result
-- to be found result'
-- to be fonud selectNestedMultiResultSet
|
Module : Database.Enumerator
Copyright : (c) 2004 Oleg Kiselyov, Alistair Bayley
License : BSD-style
Maintainer : oleg@pobox.com, alistair@abayley.org
Stability : experimental
Portability : non-portable
Abstract database interface, providing a left-fold enumerator
and cursor operations.
There is a stub: "Database.Stub.Enumerator".
This lets you run the test cases without having a working DBMS installation.
This isn't so valuable now, because it's dead easy to install Sqlite,
but it's still there if you want to try it.
Additional reading:
* <http://pobox.com/~oleg/ftp/Haskell/misc.html#fold-stream>
* <http://pobox.com/~oleg/ftp/papers/LL3-collections-enumerators.txt>
* <http://www.eros-os.org/pipermail/e-lang/2004-March/009643.html>
Note that there are a few functions that are exported from each DBMS-specific
implementation which are exposed to the API user, and which are part of
the Takusen API, but are not (necessarily) in this module.
They include:
* @connect@ (obviously DBMS specific)
* @prepareQuery, prepareLargeQuery, prepareCommand, sql, sqlbind, prefetch, cmdbind@
These functions will typically have the same names and intentions,
but their specific types and usage may differ between DBMS.
Had better keep the old style, for older versions of GHC.
> {-# OPTIONS -cpp #-}
> {-# OPTIONS -fglasgow-exts #-}
> {-# OPTIONS -fallow-overlapping-instances #-}
> {-# OPTIONS -fallow-undecidable-instances #-}
New style extension declarations.
> {-# LANGUAGE CPP #-}
> {-# LANGUAGE GeneralizedNewtypeDeriving #-}
> {-# LANGUAGE OverlappingInstances #-}
> {-# LANGUAGE UndecidableInstances #-}
> module Database.Enumerator
> (
> -- * Usage
>
> -- $usage_example
>
> -- ** Iteratee Functions
>
> -- $usage_iteratee
>
> -- ** result and result'
>
> -- $usage_result
>
> -- ** Rank-2 types, ($), and the monomorphism restriction
>
> -- $usage_rank2_types
>
> -- ** Bind Parameters
>
> -- $usage_bindparms
>
> -- ** Multiple (and nested) Result Sets
>
> -- $usage_multiresultset
>
> -- * Sessions and Transactions
> DBM -- The data constructor is not exported
> , withSession, withContinuedSession
> , commit, rollback, beginTransaction
> , withTransaction
> , IE.IsolationLevel(..)
> , execDDL, execDML, inquire
>
> -- * Exceptions and handlers
> , DBException(..)
> , formatDBException, basicDBExceptionReporter
> , reportRethrow, reportRethrowMsg
> , catchDB, catchDBError, ignoreDBError, IE.throwDB
>
> -- * Preparing and Binding
> , PreparedStmt(..) -- data constructor not exported
> , withPreparedStatement
> , withBoundStatement, IE.bindP
>
> -- * Iteratees and Cursors
> , doQuery
> , IterResult, IterAct
> , IE.currentRowNum, NextResultSet(..), RefCursor(..)
> , cursorIsEOF, cursorCurrent, cursorNext
> , withCursor
>
> -- * Utilities
> , ifNull, result, result'
> ) where
> import Prelude hiding (catch)
> import Data.Dynamic
> import Data.IORef
> import Data.Time
> import Control.Monad.Trans (liftIO)
> import Control.Exception (throw,
> dynExceptions, throwDyn, bracket, Exception, finally)
> import qualified Control.Exception (catch)
> import Control.Monad.Fix
> import Control.Monad.Reader
> import Control.Exception.MonadIO
> import qualified Database.InternalEnumerator as IE
> import Database.InternalEnumerator (DBException(..))
-----------------------------------------------------------
-----------------------------------------------------------
| 'IterResult' and 'IterAct' give us some type sugar.
Without them, the types of iteratee functions become
quite unwieldy.
> type IterResult seedType = Either seedType seedType
> type IterAct m seedType = seedType -> m (IterResult seedType)
| Catch 'Database.InteralEnumerator.DBException's thrown in the 'DBM'
monad.
> catchDB :: CaughtMonadIO m => m a -> (DBException -> m a) -> m a
> catchDB action handler = gcatch action $ \e ->
> maybe (throw e) handler (dynExceptions e >>= fromDynamic)
|This simple handler reports the error to @stdout@ and swallows it
i.e. it doesn't propagate.
> basicDBExceptionReporter :: CaughtMonadIO m => DBException -> m ()
> basicDBExceptionReporter e = liftIO (putStrLn (formatDBException e))
| This handler reports the error and propagates it
(usually to force the program to halt).
> reportRethrow :: CaughtMonadIO m => DBException -> m a
> --reportRethrow e = basicDBExceptionReporter e >> IE.throwDB e
> reportRethrow e = reportRethrowMsg "" e
| Same as reportRethrow, but you can prefix some text to the error
(perhaps to indicate which part of your program raised it).
> reportRethrowMsg :: CaughtMonadIO m => String -> DBException -> m a
> reportRethrowMsg m e = liftIO (putStr m) >> basicDBExceptionReporter e >> IE.throwDB e
| A show for 'Database.InteralEnumerator.DBException's.
> formatDBException :: DBException -> String
> formatDBException (DBError (ssc, sssc) e m) =
> ssc ++ sssc ++ " " ++ (show e) ++ ": " ++ m
> formatDBException (DBFatal (ssc, sssc) e m) =
> ssc ++ sssc ++ " " ++ (show e) ++ ": " ++ m
> formatDBException (DBUnexpectedNull r c) =
> "Unexpected null in row " ++ (show r) ++ ", column " ++ (show c) ++ "."
> formatDBException (DBNoData) = "Fetch: no more data."
|If you want to trap a specific error number, use this.
It passes anything else up.
> catchDBError :: (CaughtMonadIO m) =>
> Int -> m a -> (DBException -> m a) -> m a
> catchDBError n action handler = catchDB action
> (\dberror ->
> case dberror of
> DBError ss e m | e == n -> handler dberror
> _ | otherwise -> IE.throwDB dberror
> )
| Analogous to 'catchDBError', but ignores specific errors instead
(propagates anything else).
> ignoreDBError :: (CaughtMonadIO m) => Int -> m a -> m a
> ignoreDBError n action = catchDBError n action (\e -> return undefined)
--------------------------------------------------------------------
-- ** Session monad
--------------------------------------------------------------------
The DBM data constructor is NOT exported.
One may think to quantify over sess in |withSession|. We won't need
any mark then, I gather.
The quantification over Session is quite bothersome: need to enumerate
all class constraints for the Session (like IQuery, DBType, etc).
> newtype IE.ISession sess => DBM mark sess a = DBM (ReaderT sess IO a)
#ifndef __HADDOCK__
> deriving (Functor, Monad, MonadIO, MonadFix, MonadReader sess)
#else
> -- Haddock can't cope with the "MonadReader sess" instance
> deriving (Functor, Monad, MonadIO, MonadFix)
#endif
> unDBM (DBM x) = x
> instance IE.ISession si => CaughtMonadIO (DBM mark si) where
> gcatch a h = DBM ( gcatch (unDBM a) (unDBM . h) )
> gcatchJust p a h = DBM ( gcatchJust p (unDBM a) (unDBM . h) )
| Typeable constraint is to prevent the leakage of Session and other
marked objects.
> withSession :: (Typeable a, IE.ISession sess) =>
> IE.ConnectA sess -> (forall mark. DBM mark sess a) -> IO a
> withSession (IE.ConnectA connecta) m =
> bracket (connecta) (IE.disconnect) (runReaderT (unDBM m))
| Persistent database connections.
This issue has been brought up by Shanky Surana. The following design
is inspired by that exchange.
On one hand, implementing persistent connections is easy. One may say we should
have added them long time ago, to match HSQL, HDBC, and similar
database interfaces. Alas, implementing persistent connection
safely is another matter. The simplest design is like the following
> withContinuedSession :: (Typeable a, IE.ISession sess) =>
> IE.ConnectA sess -> (forall mark. DBM mark sess a) ->
> IO (a, IE.ConnectA sess)
> withContinuedSession (IE.ConnectA connecta) m = do
> conn <- connecta
> r <- runReaderT (unDBM m) conn
> return (r,(return conn))
so that the connection object is returned as the result and can be
used again with withContinuedSession or withSession. The problem is
that nothing prevents us from writing:
> (r1,conn) <- withContinuedSession (connect "...") query1
> r2 <- withSession conn query2
> r3 <- withSession conn query3
That is, we store the suspended connection and then use it twice.
But the first withSession closes the connection. So, the second
withSession gets an invalid session object. Invalid in a sense that
even memory may be deallocated, so there is no telling what happens
next. Also, as we can see, it is difficult to handle errors and
automatically dispose of the connections if the fatal error is
encountered.
All these problems are present in other interfaces... In the
case of a suspended connection, the problem is how to enforce the
/linear/ access to a variable. It can be enforced, via a
state-changing monad. The implementation below makes
the non-linear use of a suspended connection a run-time checkable
condition. It will be generic and safe - fatal errors close the
connection, an attempt to use a closed connection raises an error, and
we cannot reuse a connection. We have to write:
> (r1, conn1) <- withContinuedSession conn ...
> (r2, conn2) <- withContinuedSession conn1 ...
> (r3, conn3) <- withContinuedSession conn2 ...
etc. If we reuse a suspended connection or use a closed connection,
we get a run-time (exception). That is of course not very
satisfactory - and yet better than a segmentation fault.
> withContinuedSession :: (Typeable a, IE.ISession sess) =>
> IE.ConnectA sess -> (forall mark. DBM mark sess a)
> -> IO (a, IE.ConnectA sess)
> withContinuedSession (IE.ConnectA connecta) m =
> do conn <- connecta -- this invalidates connecta
> r <- runReaderT (unDBM m) conn
> `Control.Exception.catch` (\e -> IE.disconnect conn >> throw e)
> -- make a new, one-shot connecta
> hasbeenused <- newIORef False
> let connecta = do
> fl <- readIORef hasbeenused
> when fl $ error "connecta has been re-used"
> writeIORef hasbeenused True
> return conn
> return (r,IE.ConnectA connecta)
> beginTransaction ::
> (MonadReader s (ReaderT s IO), IE.ISession s) =>
> IE.IsolationLevel -> DBM mark s ()
> beginTransaction il = DBM (ask >>= \s -> lift $ IE.beginTransaction s il)
> commit :: IE.ISession s => DBM mark s ()
> commit = DBM( ask >>= lift . IE.commit )
> rollback :: IE.ISession s => DBM mark s ()
> rollback = DBM( ask >>= lift . IE.rollback )
> executeCommand :: IE.Command stmt s => stmt -> DBM mark s Int
> executeCommand stmt = DBM( ask >>= \s -> lift $ IE.executeCommand s stmt )
| DDL operations don't manipulate data, so we return no information.
If there is a problem, an exception will be raised.
> execDDL :: IE.Command stmt s => stmt -> DBM mark s ()
> execDDL stmt = executeCommand stmt >> return ()
| Returns the number of rows affected.
> execDML :: IE.Command stmt s => stmt -> DBM mark s Int
> execDML = executeCommand
| Allows arbitrary actions to be run the DBM monad.
the back-end developer must supply instances of EnvInquiry,
which is hidden away in "Database.InternalEnumerator".
An example of this is 'Database.Sqlite.Enumerator.LastInsertRowid'.
> inquire :: IE.EnvInquiry key s result => key -> DBM mark s result
> inquire key = DBM( ask >>= \s -> lift $ IE.inquire key s )
--------------------------------------------------------------------
-- ** Statements; Prepared statements
--------------------------------------------------------------------
> newtype PreparedStmt mark stmt = PreparedStmt stmt
> executePreparation :: IE.IPrepared stmt sess bstmt bo =>
> IE.PreparationA sess stmt -> DBM mark sess (PreparedStmt mark stmt)
> executePreparation (IE.PreparationA action) =
> DBM( ask >>= \sess -> lift $ action sess >>= return . PreparedStmt)
> data NextResultSet mark stmt = NextResultSet (PreparedStmt mark stmt)
> data RefCursor a = RefCursor a
The exception handling in withPreparedStatement looks awkward,
but there's a good reason...
Suppose there's some sort of error when we call destroyStmt.
The exception handler also must call destroyStmt (because the exception
might have also come from the invocation of action), but calling destroyStmt
might also raise a new exception (for example, a different error is raised
if you re-try a failed CLOSE-cursor, because the transaction is aborted).
So we wrap this call with a catch, and ensure that the original exception
is preserved and re-raised.
| Prepare a statement and run a DBM action over it.
This gives us the ability to re-use a statement,
for example by passing different bind values for each execution.
The Typeable constraint is to prevent the leakage of marked things.
The type of bound statements should not be exported (and should not be
in Typeable) so the bound statement can't leak either.
> withPreparedStatement ::
> (Typeable a, IE.IPrepared stmt sess bstmt bo)
> => IE.PreparationA sess stmt
> -- ^ preparation action to create prepared statement;
> -- this action is usually created by @prepareQuery\/Command@
> -> (PreparedStmt mark stmt -> DBM mark sess a)
> -- ^ DBM action that takes a prepared statement
> -> DBM mark sess a
> withPreparedStatement pa action = do
> ps <- executePreparation pa
> gcatch ( do
> v <- action ps
> destroyStmt ps
> return v
> ) (\e -> gcatch (destroyStmt ps >> throw e) (\_ -> throw e))
Not exported.
> destroyStmt :: (IE.ISession sess, IE.IPrepared stmt sess bstmt bo)
> => PreparedStmt mark stmt -> DBM mark sess ()
> destroyStmt (PreparedStmt stmt) = DBM( ask >>= \s -> lift $ IE.destroyStmt s stmt )
| Applies a prepared statement to bind variables to get a bound statement,
which is passed to the provided action.
Note that by the time it is passed to the action, the query or command
has usually been executed.
A bound statement would normally be an instance of
'Database.InternalEnumerator.Statement', so it can be passed to
'Database.Enumerator.doQuery'
in order to process the result-set, and also an instance of
'Database.InternalEnumerator.Command', so that we can write
re-usable DML statements (inserts, updates, deletes).
The Typeable constraint is to prevent the leakage of marked things.
The type of bound statements should not be exported (and should not be
in Typeable) so the bound statement can't leak either.
> withBoundStatement ::
> (Typeable a, IE.IPrepared stmt s bstmt bo)
> => PreparedStmt mark stmt
> -- ^ prepared statement created by withPreparedStatement
> -> [IE.BindA s stmt bo]
> -- ^ bind values
> -> (bstmt -> DBM mark s a)
> -- ^ action to run over bound statement
> -> DBM mark s a
> withBoundStatement (PreparedStmt stmt) ba f =
> DBM ( ask >>= \s ->
> lift $ IE.bindRun s stmt ba (\b -> runReaderT (unDBM (f b)) s))
--------------------------------------------------------------------
-- ** Buffers and QueryIteratee
--------------------------------------------------------------------
|The class QueryIteratee is not for the end user. It provides the
interface between the low- and the middle-layers of Takusen. The
middle-layer - enumerator - is database-independent then.
> class MonadIO m => QueryIteratee m q i seed b |
> i -> m, i -> seed, q -> b where
> iterApply :: q -> [b] -> seed -> i -> m (IterResult seed)
> allocBuffers :: q -> i -> IE.Position -> m [b]
|This instance of the class is the terminating case
i.e. where the iteratee function has one argument left.
The argument is applied, and the result returned.
> instance (IE.DBType a q b, MonadIO m) =>
> QueryIteratee m q (a -> seed -> m (IterResult seed)) seed b where
> iterApply q [buf] seed fn = do
> v <- liftIO $ IE.fetchCol q buf
> fn v seed
> allocBuffers q _ n = liftIO $
> sequence [IE.allocBufferFor (undefined::a) q n]
|This instance of the class implements the starting and continuation cases.
> instance (QueryIteratee m q i' seed b, IE.DBType a q b)
> => QueryIteratee m q (a -> i') seed b where
> iterApply q (buffer:moreBuffers) seed fn = do
> v <- liftIO $ IE.fetchCol q buffer
> iterApply q moreBuffers seed (fn v)
> allocBuffers q fn n = do
> buffer <- liftIO $ IE.allocBufferFor (undefined::a) q n
> moreBuffers <- allocBuffers q (undefined::i') (n+1)
> return (buffer:moreBuffers)
--------------------------------------------------------------------
-- ** A Query monad and cursors
--------------------------------------------------------------------
> type CollEnumerator i m s = i -> s -> m s
> type Self i m s = i -> s -> m s
> type CFoldLeft i m s = Self i m s -> CollEnumerator i m s
|A DBCursor is an IORef-mutable-pair @(a, Maybe f)@, where @a@ is the result-set so far,
and @f@ is an IO action that fetches and returns the next row (when applied to True),
or closes the cursor (when applied to False).
If @Maybe@ f is @Nothing@, then the result-set has been exhausted
(or the iteratee function terminated early),
and the cursor has already been closed.
> newtype DBCursor mark ms a =
> DBCursor (IORef (a, Maybe (Bool-> ms (DBCursor mark ms a))))
| The left-fold interface.
> doQuery :: (IE.Statement stmt sess q,
> QueryIteratee (DBM mark sess) q i seed b,
> IE.IQuery q sess b) =>
> stmt -- ^ query
> -> i -- ^ iteratee function
> -> seed -- ^ seed value
> -> DBM mark sess seed
> doQuery stmt iteratee seed = do
> (lFoldLeft, finalizer) <- doQueryMaker stmt iteratee
> gcatch (fix lFoldLeft iteratee seed)
> (\e -> do
> finalizer
> liftIO (throw e)
> )
An auxiliary function, not seen by the user.
> doQueryMaker stmt iteratee = do
> sess <- ask
> query <- liftIO $ IE.makeQuery sess stmt
> buffers <- allocBuffers query iteratee 1
> let
> finaliser =
> liftIO (mapM_ (IE.freeBuffer query) buffers)
> >> liftIO (IE.destroyQuery query)
> hFoldLeft self iteratee initialSeed = do
> let
> handle seed True = iterApply query buffers seed iteratee
> >>= handleIter
> handle seed False = (finaliser) >> return seed
> handleIter (Right seed) = self iteratee seed
> handleIter (Left seed) = (finaliser) >> return seed
> liftIO (IE.fetchOneRow query) >>= handle initialSeed
> return (hFoldLeft, finaliser)
Another auxiliary function, also not seen by the user.
> openCursor stmt iteratee seed = do
> ref <- liftIO (newIORef (seed,Nothing))
> (lFoldLeft, finalizer) <- doQueryMaker stmt iteratee
> let update v = liftIO $ modifyIORef ref (\ (_, f) -> (v, f))
> let
> close finalseed = do
> liftIO$ modifyIORef ref (\_ -> (finalseed, Nothing))
> finalizer
> return (DBCursor ref)
> let
> k' fni seed' =
> let
> k fni' seed'' = do
> let k'' flag = if flag then k' fni' seed'' else close seed''
> liftIO$ modifyIORef ref (\_->(seed'', Just k''))
> return seed''
> in do
> liftIO$ modifyIORef ref (\_ -> (seed', Nothing))
> do {lFoldLeft k fni seed' >>= update}
> return $ DBCursor ref
> k' iteratee seed
|cursorIsEOF's return value tells you if there are any more rows or not.
If you call 'cursorNext' when there are no more rows,
a 'DBNoData' exception is thrown.
Cursors are automatically closed and freed when:
* the iteratee returns @Left a@
* the query result-set is exhausted.
To make life easier, we've created a 'withCursor' function,
which will clean up if an error (exception) occurs,
or the code exits early.
You can nest them to get interleaving, if you desire:
> withCursor query1 iter1 [] $ \c1 -> do
> withCursor query2 iter2 [] $ \c2 -> do
> r1 <- cursorCurrent c1
> r2 <- cursorCurrent c2
> ...
> return something
Note that the type of the functions below is set up so to perpetuate
the mark.
> cursorIsEOF :: DBCursor mark (DBM mark s) a -> DBM mark s Bool
> cursorIsEOF (DBCursor ref) = do
> (_, maybeF) <- liftIO $ readIORef ref
> return $ maybe True (const False) maybeF
|Returns the results fetched so far, processed by iteratee function.
> cursorCurrent :: DBCursor mark (DBM mark s) a -> DBM mark s a
> cursorCurrent (DBCursor ref) = do
> (v, _) <- liftIO $ readIORef ref
> return v
|Advance the cursor. Returns the cursor. The return value is usually ignored.
> cursorNext :: DBCursor mark (DBM mark s) a
> -> DBM mark s (DBCursor mark (DBM mark s) a)
> cursorNext (DBCursor ref) = do
> (_, maybeF) <- liftIO $ readIORef ref
> maybe (IE.throwDB DBNoData) ($ True) maybeF
Returns the cursor. The return value is usually ignored.
This function is not available to the end user (i.e. not exported).
The cursor is closed automatically when its region exits.
> cursorClose c@(DBCursor ref) = do
> (_, maybeF) <- liftIO $ readIORef ref
> maybe (return c) ($ False) maybeF
|Ensures cursor resource is properly tidied up in exceptional cases.
Propagates exceptions after closing cursor.
The Typeable constraint is to prevent cursors and other marked values
(like cursor computations) from escaping.
> withCursor ::
> ( Typeable a, IE.Statement stmt sess q
> , QueryIteratee (DBM mark sess) q i seed b
> , IE.IQuery q sess b
> ) =>
> stmt -- ^ query
> -> i -- ^ iteratee function
> -> seed -- ^ seed value
> -> (DBCursor mark (DBM mark sess) seed -> DBM mark sess a) -- ^ action taking cursor parameter
> -> DBM mark sess a
> withCursor stmt iteratee seed action =
> gbracket (openCursor stmt iteratee seed) cursorClose action
Although withTransaction has the same structure as a bracket,
we can't use bracket because the resource-release action
(commit or rollback) differs between the success and failure cases.
|Perform an action as a transaction: commit afterwards,
unless there was an exception, in which case rollback.
> withTransaction :: (IE.ISession s) =>
> IE.IsolationLevel -> DBM mark s a -> DBM mark s a
>
> withTransaction isolation action = do
> beginTransaction isolation
> gcatch ( do
> v <- action
> commit
> return v
> ) (\e -> rollback >> throw e )
--------------------------------------------------------------------
-- ** Misc.
--------------------------------------------------------------------
|Useful utility function, for SQL weenies.
> ifNull :: Maybe a -- ^ nullable value
> -> a -- ^ value to substitute if first parameter is null i.e. 'Data.Maybe.Nothing'
> -> a
> ifNull value subst = maybe subst id value
| Another useful utility function.
Use this to return a value from an iteratee function (the one passed to
'Database.Enumerator.doQuery').
Note that you should probably nearly always use the strict version.
> result :: (Monad m) => IterAct m a
> result x = return (Right x)
|A strict version. This is recommended unless you have a specific need for laziness,
as the lazy version will gobble stack and heap.
If you have a large result-set (in the order of 10-100K rows or more),
it is likely to exhaust the standard 1M GHC stack.
Whether or not 'result' eats memory depends on what @x@ does:
if it's a delayed computation then it almost certainly will.
This includes consing elements onto a list,
and arithmetic operations (counting, summing, etc).
> result' :: (Monad m) => IterAct m a
> result' x = return (Right $! x)
That's the code... now for the documentation.
====================================================================
== Usage notes
====================================================================
$usage_example
Let's look at some example code:
> -- sample code, doesn't necessarily compile
> module MyDbExample is
>
> import Database.Oracle.Enumerator
> import Database.Enumerator
> ...
>
> query1Iteratee :: (Monad m) => Int -> String -> Double -> IterAct m [(Int, String, Double)]
> query1Iteratee a b c accum = result' ((a, b, c):accum)
>
> -- non-query actions.
> otherActions session = do
> execDDL (sql "create table blah")
> execDML (sql "insert into blah ...")
> commit
> -- Use withTransaction to delimit a transaction.
> -- It will commit at the end, or rollback if an error occurs.
> withTransaction Serialisable ( do
> execDML (sql "update blah ...")
> execDML (sql "insert into blah ...")
> )
>
> main :: IO ()
> main = do
> withSession (connect "user" "password" "server") ( do
> -- simple query, returning reversed list of rows.
> r <- doQuery (sql "select a, b, c from x") query1Iteratee []
> liftIO $ putStrLn $ show r
> otherActions session
> )
Notes:
* connection is made by 'Database.Enumerator.withSession',
which also disconnects when done i.e. 'Database.Enumerator.withSession'
delimits the connection.
You must pass it a connection action, which is back-end specific,
and created by calling the 'Database.Sqlite.Enumerator.connect'
function from the relevant back-end.
* inside the session, the usual transaction delimiter commands are usable
e.g. 'Database.Enumerator.beginTransaction' 'Database.InternalEnumerator.IsolationLevel',
'Database.Enumerator.commit', 'Database.Enumerator.rollback', and
'Database.Enumerator.withTransaction'.
We also provide 'Database.Enumerator.execDML' and 'Database.Enumerator.execDDL'.
* non-DML and -DDL commands - i.e. queries - are processed by
'Database.Enumerator.doQuery' (this is the API for our left-fold).
See more explanation and examples below in /Iteratee Functions/ and
/Bind Parameters/ sections.
The first argument to 'Database.Enumerator.doQuery' must be an instance of
'Database.InternalEnumerator.Statement'.
Each back-end will provide a useful set of @Statement@ instances
and associated constructor functions for them.
For example, currently all back-ends have:
* for basic, all-text statements (no bind variables, default row-caching)
which can be used as queries or commands:
> sql "select ..."
* for a select with bind variables:
> sqlbind "select ..." [bindP ..., bindP ...]
* for a select with bind variables and row caching:
> prefetch 100 "select ..." [bindP ..., bindP ...]
* for a DML command with bind variables:
> cmdbind "insert into ..." [bindP ..., bindP ...]
* for a reusable prepared statement: we have to first create the
prepared statement, and then bind in a separate step.
This separation lets us re-use prepared statements:
> let stmt = prepareQuery (sql "select ...")
> withPreparedStatement stmt $ \pstmt ->
> withBoundStatement pstmt [bindP ..., bindP ...] $ \bstmt -> do
> result <- doQuery bstmt iter seed
> ...
The PostgreSQL backend additionally requires that when preparing statements,
you (1) give a name to the prepared statement,
and (2) specify types for the bind parameters.
The list of bind-types is created by applying the
'Database.PostgreSQL.Enumerator.bindType' function
to dummy values of the appropriate types. e.g.
> let stmt = prepareQuery "stmtname" (sql "select ...") [bindType "", bindType (0::Int)]
> withPreparedStatement stmt $ \pstmt -> ...
A longer explanation of prepared statements and
bind variables is in the Bind Parameters section below.
$usage_iteratee
'Database.Enumerator.doQuery' takes an iteratee function, of n arguments.
Argument n is the accumulator (or seed).
For each row that is returned by the query,
the iteratee function is called with the data from that row in
arguments 1 to n-1, and the current accumulated value in the argument n.
The iteratee function returns the next value of the accumulator,
wrapped in an 'Data.Either.Either'.
If the 'Data.Either.Either' value is @Left@, then the query will terminate,
returning the wrapped accumulator\/seed value.
If the value is @Right@, then the query will continue, with the next row
begin fed to the iteratee function, along with the new accumulator\/seed value.
In the example above, @query1Iteratee@ simply conses the new row (as a tuple)
to the front of the accumulator.
The initial seed passed to 'Database.Enumerator.doQuery' was an empty list.
Consing the rows to the front of the list results in a list
with the rows in reverse order.
The types of values that can be used as arguments to the iteratee function
are back-end specific; they must be instances of the class
'Database.InternalEnumerator.DBType'.
Most backends directly support the usual lowest-common-denominator set
supported by most DBMS's: 'Data.Int.Int', 'Data.Char.String',
'Prelude.Double', 'Data.Time.UTCTime'.
('Data.Int.Int64' is often, but not always, supported.)
By directly support we mean there is type-specific marshalling code
implemented.
Indirect support for 'Text.Read.Read'- and 'Text.Show.Show'-able types
is supported by marshalling to and from 'Data.Char.String's.
This is done automatically by the back-end;
there is no need for user-code to perform the marshalling,
as long as instances of 'Text.Read.Read' and 'Text.Show.Show' are defined.
The iteratee function operates in the 'DBM' monad,
so if you want to do IO in it you must use 'Control.Monad.Trans.liftIO'
(e.g. @liftIO $ putStrLn \"boo\"@ ) to lift the IO action into 'DBM'.
The iteratee function is not restricted to just constructing lists.
For example, a simple counter function would ignore its arguments,
and the accumulator would simply be the count e.g.
> counterIteratee :: (Monad m) => Int -> IterAct m Int
> counterIteratee _ i = result' $ (1 + i)
The iteratee function that you pass to 'Database.Enumerator.doQuery'
needs type information,
at least for the arguments if not the return type (which is typically
determined by the type of the seed).
The type synonyms 'IterAct' and 'IterResult' give some convenience
in writing type signatures for iteratee functions:
> type IterResult seedType = Either seedType seedType
> type IterAct m seedType = seedType -> m (IterResult seedType)
Without them, the type for @counterIteratee@ would be:
> counterIteratee :: (Monad m) => Int -> Int -> m (Either Int Int)
which doesn't seem so onerous, but for more elaborate seed types
(think large tuples) it certainly helps e.g.
> iter :: Monad m =>
> String -> Double -> CalendarTime -> [(String, Double, CalendarTime)]
> -> m (Either [(String, Double, CalendarTime)] [(String, Double, CalendarTime)] )
reduces to (by using 'IterAct' and 'IterResult'):
> iter :: Monad m =>
> String -> Double -> CalendarTime -> IterAct m [(String, Double, CalendarTime)]
$usage_result
The 'result' (lazy) and @result\'@ (strict) functions are another convenient shorthand
for returning values from iteratee functions. The return type from an iteratee is actually
@Either seed seed@, where you return @Right@ if you want processing to continue,
or @Left@ if you want processing to stop before the result-set is exhausted.
The common case is:
> query1Iteratee a b c accum = return (Right ((a, b, c):accum))
which we can write as
> query1Iteratee a b c accum = result $ (a, b, c):accum)
We have lazy and strict versions of @result@. The strict version is almost certainly
the one you want to use. If you come across a case where the lazy function is useful,
please tell us about it. The lazy function tends to exhaust the stack for large result-sets,
whereas the strict function does not.
This is due to the accumulation of a large number of unevaluated thunks,
and will happen even for simple arithmetic operations such as counting or summing.
If you use the lazy function and you have stack\/memory problems, do some profiling.
With GHC:
* ensure the iteratee has its own cost-centre (make it a top-level function)
* compile with @-prof -auto-all@
* run with @+RTS -p -hr -RTS@
* run @hp2ps@ over the resulting @.hp@ file to get a @.ps@ document, and take a look at it.
Retainer sets are listed on the RHS, and are prefixed with numbers e.g. (13)CAF, (2)SYSTEM.
At the bottom of the @.prof@ file you'll find the full descriptions of the retainer sets.
Match the number in parentheses on the @.ps@ graph with a SET in the @.prof@ file;
the one at the top of the @.ps@ graph is the one using the most memory.
You'll probably find that the lazy iteratee is consuming all of the stack with lazy thunks,
which is why we recommend the strict function.
$usage_rank2_types
In some examples we use the application operator ($) instead of parentheses
(some might argue that this is a sign of developer laziness).
At first glance, ($) and conventional function application via juxtaposition
seem to be interchangeable e.g.
> liftIO (putStrLn (show x))
looks equivalent to
> liftIO $ putStrLn $ show x
But they're not, because Haskell's type system gives us a nice compromise.
In a Hindley-Milner type system (like ML) there is no difference between
($) and function application, because polymorphic functions are not
first-class and cannot be passed to other functions.
At the other end of the scale, ($) and function application in System F
are equivalent, because polymorphic functions can be passed to other
functions. However, type inference in System F is undecidable.
Haskell hits the sweet spot: maintaining full inference,
and permitting rank-2 polymorphism, in exchange for very few
type annotations. Only functions that take polymorphic functions (and
thus are higher-rank) need type signatures. Rank-2 types can't be
inferred. The function ($) is a regular, rank-1 function, and so
it can't take polymorphic functions as arguments and return
polymorphic functions.
Here's an example where ($) fails:
we supply a simple test program in the README file.
If you change the @withSession@ line to use ($), like so
(and remove the matching end-parenthese):
> withSession (connect "sqlite_db") $ do
then you get the error:
> Main.hs:7:38:
> Couldn't match expected type `forall mark. DBM mark Session a'
> against inferred type `a1 b'
> In the second argument of `($)', namely
> ...
Another way of rewriting it is like this, where we separate the
'Database.Enumerator.DBM' action into another function:
> {-# OPTIONS -fglasgow-exts #-}
> module Main where
> import Database.Sqlite.Enumerator
> import Control.Monad.Trans (liftIO)
> main = flip catchDB reportRethrow $
> withSession (connect "sqlite_db") hello
>
> hello = withTransaction RepeatableRead $ do
> let iter (s::String) (_::String) = result s
> result <- doQuery (sql "select 'Hello world.'") iter ""
> liftIO (putStrLn result)
which gives this error:
> Main.hs:9:2:
> Inferred type is less polymorphic than expected
> Quantified type variable `mark' is mentioned in the environment:
> hello :: DBM mark Session () (bound at Main.hs:15:0)
> ...
This is just the monomorphism restriction in action.
Sans a type signature, the function `hello' is monomorphised
(that is, `mark' is replaced with (), per GHC rules).
This is easily fixed by adding this type declaration:
> hello :: DBM mark Session ()
$usage_bindparms
Support for bind variables varies between DBMS's.
We call 'Database.Enumerator.withPreparedStatement' function to prepare
the statement, and then call 'Database.Enumerator.withBoundStatement'
to provide the bind values and execute the query.
The value returned by 'Database.Enumerator.withBoundStatement'
is an instance of the 'Database.InternalEnumerator.Statement' class,
so it can be passed to 'Database.Enumerator.doQuery' for result-set processing.
When we call 'Database.Enumerator.withPreparedStatement', we must pass
it a \"preparation action\", which is simply an action that returns
the prepared query. The function to create this action varies between backends,
and by convention is called 'Database.PostgreSQL.Enumerator.prepareQuery'.
For DML statements, you must use 'Database.PostgreSQL.Enumerator.prepareCommand',
as the library needs to do something different depending on whether or not the
statement returns a result-set.
For queries with large result-sets, we provide
'Database.PostgreSQL.Enumerator.prepareLargeQuery',
which takes an extra parameter: the number of rows to prefetch
in a network call to the server.
This aids performance in two ways:
1. you can limit the number of rows that come back to the
client, in order to use less memory, and
2. the client library will cache rows, so that a network call to
the server is not required for every row processed.
With PostgreSQL, we must specify the types of the bind parameters
when the query is prepared, so the 'Database.PostgreSQL.Enumerator.prepareQuery'
function takes a list of 'Database.PostgreSQL.Enumerator.bindType' values.
Also, PostgreSQL requires that prepared statements are named,
although you can use \"\" as the name.
With Sqlite and Oracle, we simply pass the query text to
'Database.PostgreSQL.Sqlite.prepareQuery',
so things are slightly simpler for these backends.
Perhaps an example will explain it better:
> postgresBindExample = do
> let
> query = sql "select blah from blahblah where id = ? and code = ?"
> iter :: (Monad m) => String -> IterAct m [String]
> iter s acc = result $ s:acc
> bindVals = [bindP (12345::Int), bindP "CODE123"]
> bindTypes = [bindType (0::Int), bindType ""]
> withPreparedStatement (prepareQuery "stmt1" query bindTypes) $ \pstmt -> do
> withBoundStatement pstmt bindVals $ \bstmt -> do
> actual <- doQuery bstmt iter []
> liftIO (print actual)
Note that we pass @bstmt@ to 'Database.Enumerator.doQuery';
this is the bound statement object created by
'Database.Enumerator.withBoundStatement'.
The Oracle\/Sqlite example code is almost the same, except for the
call to 'Database.Sqlite.Enumerator.prepareQuery':
> sqliteBindExample = do
> let
> query = sql "select blah from blahblah where id = ? and code = ?"
> iter :: (Monad m) => String -> IterAct m [String]
> iter s acc = result $ s:acc
> bindVals = [bindP (12345::Int), bindP "CODE123"]
> withPreparedStatement (prepareQuery query) $ \pstmt -> do
> withBoundStatement pstmt bindVals $ \bstmt -> do
> actual <- doQuery bstmt iter []
> liftIO (print actual)
It can be a bit tedious to always use the @withPreparedStatement+withBoundStatement@
combination, so for the case where you don't plan to re-use the query,
we support a short-cut for bundling the query text and parameters.
The next example is valid for PostgreSQL, Sqlite, and Oracle
(the Sqlite implementation provides a dummy 'Database.Sqlite.Enumerator.prefetch'
function to ensure we have a consistent API).
Sqlite has no facility for prefetching - it's an embedded database, so no
network round-trip - so the Sqlite implementation ignores the prefetch count:
> bindShortcutExample = do
> let
> iter :: (Monad m) => String -> IterAct m [String]
> iter s acc = result $ s:acc
> bindVals = [bindP (12345::Int), bindP "CODE123"]
> query = prefetch 1000 "select blah from blahblah where id = ? and code = ?" bindVals
> actual <- doQuery query iter []
> liftIO (print actual)
A caveat of using prefetch with PostgreSQL is that you must be inside a transaction.
This is because the PostgreSQL implementation uses a cursor and \"FETCH FORWARD\"
to implement fetching a block of rows in a single network call,
and PostgreSQL requires that cursors are only used inside transactions.
It can be as simple as wrapping calls to 'Database.Enumerator.doQuery' by
'Database.Enumerator.withTransaction',
or you may prefer to delimit your transactions elsewhere (the API supports
'Database.InternalEnumerator.beginTransaction' and
'Database.InternalEnumerator.commit', if you prefer to use them):
> withTransaction RepeatableRead $ do
> actual <- doQuery query iter []
> liftIO (print actual)
You may have noticed that for 'Data.Int.Int' and 'Prelude.Double' literal
bind values, we have to tell the compiler the type of the literal.
I assume this is due to interaction (which I don't fully understand and therefore
cannot explain in any detail) with the numeric literal defaulting mechanism.
For non-numeric literals the compiler can determine the correct types to use.
If you omit type information for numeric literals, from GHC the error
message looks something like this:
> Database/PostgreSQL/Test/Enumerator.lhs:194:4:
> Overlapping instances for Database.InternalEnumerator.DBBind a
> Session
> Database.PostgreSQL.PGEnumerator.PreparedStmt
> Database.PostgreSQL.PGEnumerator.BindObj
> arising from use of `bindP' at Database/PostgreSQL/Test/Enumerator.lhs:194:4-8
> Matching instances:
> Imported from Database.PostgreSQL.PGEnumerator:
> instance (Database.InternalEnumerator.DBBind (Maybe a)
> Session
> Database.PostgreSQL.PGEnumerator.PreparedStmt
> Database.PostgreSQL.PGEnumerator.BindObj) =>
> Database.InternalEnumerator.DBBind a
> Session
> Database.PostgreSQL.PGEnumerator.PreparedStmt
> Database.PostgreSQL.PGEnumerator.BindObj
> Imported from Database.PostgreSQL.PGEnumerator:
> instance Database.InternalEnumerator.DBBind (Maybe Double)
> ....
$usage_multiresultset
Support for returning multiple result sets from a single
statement exists for PostgreSQL and Oracle.
Such functionality does not exist in Sqlite.
The general idea is to invoke a database procedure or function which
returns cursor variables. The variables can be processed by
'Database.Enumerator.doQuery' in one of two styles: linear or nested.
/Linear style:/
If we assume the existence of the following PostgreSQL function,
which is used in the test suite in "Database.PostgreSQL.Test.Enumerator":
> CREATE OR REPLACE FUNCTION takusenTestFunc() RETURNS SETOF refcursor AS $$
> DECLARE refc1 refcursor; refc2 refcursor;
> BEGIN
> OPEN refc1 FOR SELECT n*n from t_natural where n < 10 order by 1;
> RETURN NEXT refc1;
> OPEN refc2 FOR SELECT n, n*n, n*n*n from t_natural where n < 10 order by 1;
> RETURN NEXT refc2;
> END;$$ LANGUAGE plpgsql;
... then this code shows how linear processing of cursors would be done:
> withTransaction RepeatableRead $ do
> withPreparedStatement (prepareQuery "stmt1" (sql "select * from takusenTestFunc()") []) $ \pstmt -> do
> withBoundStatement pstmt [] $ \bstmt -> do
> dummy <- doQuery bstmt iterMain []
> result1 <- doQuery (NextResultSet pstmt) iterRS1 []
> result2 <- doQuery (NextResultSet pstmt) iterRS2 []
> where
> iterMain :: (Monad m) => (RefCursor String) -> IterAct m [RefCursor String]
> iterMain c acc = result (acc ++ [c])
> iterRS1 :: (Monad m) => Int -> IterAct m [Int]
> iterRS1 i acc = result (acc ++ [i])
> iterRS2 :: (Monad m) => Int -> Int -> Int -> IterAct m [(Int, Int, Int)]
> iterRS2 i i2 i3 acc = result (acc ++ [(i, i2, i3)])
Notes:
* the use of a 'Database.Enumerator.RefCursor' 'Data.Char.String'
type in the iteratee function indicates
to the backend that it should save each cursor value returned,
which it does by stuffing them into a list attached to the
prepared statement object.
This means that we /must/ use 'Database.Enumerator.withPreparedStatement'
to create a prepared statement object; the prepared statament oject
is the container for the cursors returned.
* in this example we choose to discard the results of the first iteratee.
This is not necessary, but in this case the only column is a
'Database.Enumerator.RefCursor', and the values are already saved
in the prepared statement object.
* saved cursors are consumed one-at-a-time by calling 'Database.Enumerator.doQuery',
passing 'Database.Enumerator.NextResultSet' @pstmt@
(i.e. passing the prepared statement oject wrapped by
'Database.Enumerator.NextResultSet').
This simply pulls the next cursor off the list
- they're processed in the order they were pushed on (FIFO) -
and processes it with the given iteratee.
* if you try to process too many cursors i.e. make too many calls
to 'Database.Enumerator.doQuery' passing 'Database.Enumerator.NextResultSet' @pstmt@,
then an exception will be thrown.
OTOH, failing to process returned cursors will not raise errors,
but the cursors will remain open on the server according to whatever scoping
rules the server applies.
For PostgreSQL, this will be until the transaction (or session) ends.
/Nested style:/
The linear style of cursor processing is the only style supported by
MS SQL Server and ODBC (which we do not yet support).
However, PostgreSQL and Oracle also support using nested cursors in queries.
Again for PostgreSQL, assuming we have these functions in the database:
> CREATE OR REPLACE FUNCTION takusenTestFunc(lim int4) RETURNS refcursor AS $$
> DECLARE refc refcursor;
> BEGIN
> OPEN refc FOR SELECT n, takusenTestFunc2(n) from t_natural where n < lim order by n;
> RETURN refc;
> END; $$ LANGUAGE plpgsql;
> CREATE OR REPLACE FUNCTION takusenTestFunc2(lim int4) RETURNS refcursor AS $$
> DECLARE refc refcursor;
> BEGIN
> OPEN refc FOR SELECT n from t_natural where n < lim order by n;
> RETURN refc;
> END; $$ LANGUAGE plpgsql;
... then this code shows how nested queries might work:
> selectNestedMultiResultSet = do
> let
> q = "SELECT n, takusenTestFunc(n) from t_natural where n < 10 order by n"
> iterMain (i::Int) (c::RefCursor String) acc = result' ((i,c):acc)
> iterInner (i::Int) (c::RefCursor String) acc = result' ((i,c):acc)
> iterInner2 (i::Int) acc = result' (i:acc)
> withTransaction RepeatableRead $ do
> rs <- doQuery (sql q) iterMain []
> flip mapM_ rs $ \(outer, c) -> do
> rs <- doQuery c iterInner []
> flip mapM_ rs $ \(inner, c) -> do
> rs <- doQuery c iterInner2 []
> flip mapM_ rs $ \i -> do
> liftIO (putStrLn (show outer ++ " " ++ show inner ++ " " ++ show i))
Just to make it clear: the outer query returns a result-set that includes
a 'Database.Enumerator.RefCursor' column. Each cursor from that column is passed to
'Database.Enumerator.doQuery' to process it's result-set;
here we use 'Control.Monad.mapM_' to apply an IO action to the list returned by
'Database.Enumerator.doQuery'.
For Oracle the example is slightly different.
The reason it's different is that:
* Oracle requires that the parent cursor must remain open
while processing the children
(in the PostgreSQL example, 'Database.Enumerator.doQuery'
closes the parent cursor after constructing the list,
before the list is processed. This is OK because PostgreSQL
keeps the child cursors open on the server until they are explicitly
closed, or the transaction or session ends).
* our current Oracle implementation prevents marshalling
of the cursor in the result-set buffer to a Haskell value,
so each fetch overwrites the buffer value with a new cursor.
This means you have to fully process a given cursor before
fetching the next one.
Contrast this with the PostgreSQL example above,
where the entire result-set is processed to give a
list of RefCursor values, and then we run a list of actions
over this list with 'Control.Monad.mapM_'.
This is possible because PostgreSQL refcursors are just the
database cursor names, which are Strings, which we can marshal
to Haskell values easily.
> selectNestedMultiResultSet = do
> let
> q = "select n, cursor(SELECT nat2.n, cursor"
> ++ " (SELECT nat3.n from t_natural nat3 where nat3.n < nat2.n order by n)"
> ++ " from t_natural nat2 where nat2.n < nat.n order by n)"
> ++ " from t_natural nat where n < 10 order by n"
> iterMain (outer::Int) (c::RefCursor StmtHandle) acc = do
> rs <- doQuery c (iterInner outer) []
> result' ((outer,c):acc)
> iterInner outer (inner::Int) (c::RefCursor StmtHandle) acc = do
> rs <- doQuery c (iterInner2 outer inner) []
> result' ((inner,c):acc)
> iterInner2 outer inner (i::Int) acc = do
> liftIO (putStrLn (show outer ++ " " ++ show inner ++ " " ++ show i))
> result' (i:acc)
> withTransaction RepeatableRead $ do
> rs <- doQuery (sql q) iterMain []
> return ()
Note that the PostgreSQL example can also be written like this
(except, of course, that the actual query text is that
from the PostgreSQL example).
--------------------------------------------------------------------
-- Haddock notes:
--------------------------------------------------------------------
The best way (that I've found) to get a decent introductory/explanatory
section for the module is to break the explanation into named chunks
(these begin with -- $<chunk-name>),
put the named chunks at the end, and reference them in the export list.
You *can* write the introduction inline, as part of the module description,
but Haddock has no way to make headings.
Instead, if you make an explicit export-list then you can use
the "-- *", "-- **", etc, syntax to give section headings.
(Note: if you don't use an explicit export list, then Haddock will use "-- *" etc
comments to make headings. The headings will appear in the docs in the the locations
as they do in the source, as do functions, data types, etc.)
- One blank line continues a comment block. Two or more end it.
- The module comment must contain a empty line between "Portability: ..." and the description.
- bullet-lists:
- items must be preceded by an empty line.
- each list item must start with "*".
- code-sections:
- must be preceded by an empty line.
- use " >" rather than @...@, because "@" allows markup translation, where " >" doesn't.
- @inline code (monospaced font)@
- /emphasised text/
- links: "Another.Module", 'someIdentifier' (same module),
'Another.Module.someIdentifier', <http:/www.haskell.org/haddock>
|