| Module : Database.Oracle.Test.Enumerator Copyright : (c) 2004 Oleg Kiselyov, Alistair Bayley License : BSD-style Maintainer : oleg@pobox.com, alistair@abayley.org Stability : experimental Portability : non-portable The Oracle multi-result-set tests require these database objects: CREATE OR REPLACE VIEW t_whole as select 0 as n from dual union select 1 from dual union select 2 from dual union select 3 from dual union select 4 from dual union select 5 from dual union select 6 from dual union select 7 from dual union select 8 from dual union select 9 from dual ; CREATE OR REPLACE VIEW t_natural as select n from ( select t1.n + 10 * t10.n + 100 * t100.n as n from t_whole t1, t_whole t10, t_whole t100 ) t where n > 0 order by 1 ; create or replace package Takusen as type RefCursor is ref cursor; end; CREATE OR REPLACE PROCEDURE takusenTestProc (refc1 out Takusen.RefCursor, refc2 out Takusen.RefCursor) AS BEGIN OPEN refc1 FOR SELECT n*n from t_natural where n < 10 order by 1; OPEN refc2 FOR SELECT n, n*n, n*n*n from t_natural where n < 10 order by 1; END; 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; > {-# LANGUAGE OverlappingInstances #-} > module Database.Oracle.Test.Enumerator (runTest) where > import qualified Database.Oracle.Test.OCIFunctions as Low > import Database.Oracle.Enumerator > import Database.Util (print_) > import Database.Test.Performance as Perf > import Database.Test.Enumerator > import Control.Monad (when) > import Control.Monad.Trans (liftIO) > import Test.MiniUnit > import Data.Int > import System.Time > runTest :: Perf.ShouldRunTests -> [String] -> IO () > runTest runPerf args = do > let (user:pswd:dbname:_) = args > Low.runTest args > flip catchDB basicDBExceptionReporter $ > withSession (connect user pswd dbname) (testBody runPerf) > testBody runPerf = do > runFixture OracleFunctions > when (runPerf == Perf.RunTests) runPerformanceTests > runPerformanceTests = do > makeFixture execDrop execDDL_ > beginTransaction RepeatableRead > runTestTT "Oracle performance tests" (map (flip catchDB reportRethrow) > [ timedSelect (prefetch 1000 sqlRows2Power20 []) 30 (2^20) > , timedSelect (prefetch 1 sqlRows2Power17 []) 60 (2^17) > , timedSelect (prefetch 1000 sqlRows2Power17 []) 6 (2^17) > , timedCursor (prefetch 1 sqlRows2Power17 []) 60 (2^17) > , timedCursor (prefetch 1000 sqlRows2Power17 []) 6 (2^17) > ] > ) > commit > destroyFixture execDDL_ > runFixture :: OracleFunctions -> DBM mark Session () > runFixture fns = do > makeFixture execDrop execDDL_ > execDDL_ makeFixtureMultiResultSet1 > execDDL_ makeFixtureMultiResultSet2 > execDDL_ makeFixtureMultiResultSet3 > execDDL_ makeFixtureMultiResultSet4 > execDDL_ makeFixtureBindOutput > runTestTT "Oracle tests" (map (runOneTest fns) testList) > execDDL_ dropFixtureBindOutput > execDDL_ dropFixtureMultiResultSet4 > execDDL_ dropFixtureMultiResultSet3 > execDDL_ dropFixtureMultiResultSet2 > execDDL_ dropFixtureMultiResultSet1 > destroyFixture execDDL_ > runOneTest fns t = catchDB (t fns) reportRethrow ----------------------------------------------------------- > selectNoRows _ = selectTest sqlNoRows iterNoRows expectNoRows > selectTerminatesEarly _ = selectTest sqlTermEarly iterTermEarly expectTermEarly > selectFloatsAndInts fns = selectTest (sqlFloatsAndInts fns) iterFloatsAndInts expectFloatsAndInts > selectNullString _ = selectTest sqlNullString iterNullString expectNullString > selectEmptyString _ = selectTest sqlEmptyString iterEmptyString expectEmptyString > selectUnhandledNull _ = catchDB ( do > selectTest sqlUnhandledNull iterUnhandledNull expectUnhandledNull > assertFailure sqlUnhandledNull > ) (\e -> return () ) > selectNullDate dateFn = selectTest (sqlNullDate dateFn) iterNullDate expectNullDate > selectDate dateFn = selectTest (sqlDate dateFn) iterDate expectDate > selectCalDate dateFn = selectTest (sqlDate dateFn) iterCalDate expectCalDate > selectBoundaryDates dateFn = selectTest (sqlBoundaryDates dateFn) iterBoundaryDates expectBoundaryDates > selectCursor fns = actionCursor (sqlCursor fns) > selectExhaustCursor fns = actionExhaustCursor (sqlCursor fns) > selectBindString _ = actionBindString > (prepareQuery (sql sqlBindString)) > [bindP "a2", bindP "b1"] > selectBindInt _ = actionBindInt > (prepareQuery (sql sqlBindInt)) > [bindP (1::Int), bindP (2::Int)] > selectBindIntDoubleString _ = actionBindIntDoubleString > (prefetch 0 sqlBindIntDoubleString [bindP (1::Int), bindP (2.2::Double), bindP "row 1", bindP (3::Int), bindP (4.4::Double), bindP "row 2"]) > selectBindDate _ = actionBindDate > (prefetch 1 sqlBindDate (map bindP expectBindDate)) > selectBindBoundaryDates _ = actionBindBoundaryDates > (prefetch 1 sqlBindBoundaryDates (map bindP expectBoundaryDates)) > selectRebindStmt _ = actionRebind (prepareQuery (sql sqlRebind)) > [bindP (1::Int)] [bindP (2::Int)] > boundStmtDML _ = actionBoundStmtDML (prepareCommand (sql sqlBoundStmtDML)) > boundStmtDML2 _ = withTransaction RepeatableRead $ do > count <- execDML (cmdbind sqlBoundStmtDML [bindP (100::Int), bindP "100"]) > assertEqual sqlBoundStmtDML 1 count > rollback > polymorphicFetchTest _ = actionPolymorphicFetch > (prefetch 0 sqlPolymorphicFetch [bindP expectPolymorphicFetch]) > polymorphicFetchTestNull _ = actionPolymorphicFetchNull > (prefetch 1 sqlPolymorphicFetchNull []) > exceptionRollback _ = actionExceptionRollback sqlInsertTest4 sqlExceptionRollback FIXME check definitions of whole and natural numbers > dropFixtureMultiResultSet1 = "DROP VIEW t_whole" > makeFixtureMultiResultSet1 = "CREATE OR REPLACE VIEW t_whole as" > ++ " select 0 as n from dual union select 1 from dual" > ++ " union select 2 from dual union select 3 from dual" > ++ " union select 4 from dual union select 5 from dual" > ++ " union select 6 from dual union select 7 from dual" > ++ " union select 8 from dual union select 9 from dual" > dropFixtureMultiResultSet2 = "DROP VIEW t_natural" > makeFixtureMultiResultSet2 = "CREATE OR REPLACE VIEW t_natural as" > ++ " select n from" > ++ " ( select t1.n + 10 * t10.n + 100 * t100.n as n" > ++ " from t_whole t1, t_whole t10, t_whole t100" > ++ " ) t where n > 0 order by 1" > dropFixtureMultiResultSet3 = "DROP package Takusen" > makeFixtureMultiResultSet3 = > "create or replace package Takusen as type RefCursor is ref cursor; end;" > dropFixtureMultiResultSet4 = "DROP PROCEDURE takusenTestProc" > makeFixtureMultiResultSet4 = "CREATE OR REPLACE PROCEDURE takusenTestProc" > ++ " (refc1 out Takusen.RefCursor, refc2 out Takusen.RefCursor) AS BEGIN" > ++ " OPEN refc1 FOR SELECT n*n from t_natural where n < 10 order by 1;" > ++ " OPEN refc2 FOR SELECT n, n*n, n*n*n from t_natural where n < 10 order by 1;" > ++ " END;" What would you need to do to make database functions and procedures callable from Haskell as if they were local functions (IO actions)? wrapPLSQLFunc funcname parms = let sqltext = "begin " ++ (head args) ++ " := " ++ funcname ++ "(" ++ placeholders ++ "); end;" placeholders = concat (intersperse "," (tail args)) args = take (length parms) (map (\n -> ":x" ++ show n) [1..]) in cmdbind sqltext parms wrapPLSQLProc procname parms = let sqltext = "begin " ++ procname ++ "(" ++ placeholders ++ "); end;" placeholders = concat (intersperse "," args) args = take (length parms) (map (\n -> ":x" ++ show n) [1..]) in cmdbind sqltext parms convertCcy :: String -> Double -> String -> UTCTime -> DBM mark Session (String, Double) convertCcy ccyFrom valFrom ccyTo onDate = do sqlcmd = wrapPLSQLFunc "pk_fx.convert_ccy" [ bindP (Out (0 :: Double)) , bindP ccyFrom , bindP valFrom , bindP ccyTo , bindP onDate ] let iter :: Monad m => Double -> IterAct m Double iter val seed = return (Left val) result = doQuery sqlcmd iter undefined return result > selectMultiResultSet _ = do > let refcursor :: Maybe StmtHandle; refcursor = Just undefined > withTransaction RepeatableRead $ do > withPreparedStatement (prepareCommand (sql "begin takusenTestProc(:1,:2); end;")) $ \pstmt -> do > withBoundStatement pstmt [bindP (Out refcursor), bindP (Out refcursor)] $ \bstmt -> do > dummy <- doQuery bstmt iterMain () > result1 <- doQuery (NextResultSet pstmt) iterRS1 [] > assertEqual "selectMultiResultSet: RS1" [1,4,9,16,25,36,49,64,81] result1 > result2 <- doQuery (NextResultSet pstmt) iterRS2 [] > let expect = [(1,1,1),(2,4,8),(3,9,27),(4,16,64),(5,25,125),(6,36,216) > ,(7,49,343),(8,64,512),(9,81,729)] > assertEqual "selectMultiResultSet: RS2" expect result2 > return () > where > iterMain :: (Monad m) => RefCursor StmtHandle -> RefCursor StmtHandle -> IterAct m () > iterMain c1 c2 acc = return (Left acc) > 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)]) > selectNestedMultiResultSet :: OracleFunctions -> DBM mark Session () > selectNestedMultiResultSet _ = do > let > -- This returns two rows, each of which contains one cursor. > -- The first cursor returns 101, the second 102. > q = "select cursor(select n from dual) from" > ++ " (select 101 as n from dual union select 102 from dual)" > iterMain (c::RefCursor StmtHandle) acc = do > rs <- doQuery c iterInner [] > result' (c:acc) > iterInner (i::Int) acc = do > if (i /= 101 && i /= 102) > then assertFailure "selectNestedMultiResultSet: inner value not 101 or 102" > else return () > result' (i:acc) > withTransaction RepeatableRead $ do > withPreparedStatement (prepareQuery (sql q)) $ \pstmt -> do > withBoundStatement pstmt [] $ \bstmt -> do > rs <- doQuery bstmt iterMain [] > return () > selectNestedMultiResultSet2 :: OracleFunctions -> DBM mark Session () > selectNestedMultiResultSet2 _ = 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) [] > let expect = drop (9-outer) [8,7,6,5,4,3,2,1] > assertEqual "processOuter" expect (map fst rs) > result' ((outer,c):acc) > iterInner outer (inner::Int) (c::RefCursor StmtHandle) acc = do > rs <- doQuery c (iterInner2 outer inner) [] > let expect = drop (9-inner) [8,7,6,5,4,3,2,1] > assertEqual "processInner" expect rs > result' ((inner,c):acc) > iterInner2 outer inner (i::Int) acc = do > --print_ (show outer ++ " " ++ show inner ++ " " ++ show i) > assertBool "processInner2" (i < inner) > result' (i:acc) > withTransaction RepeatableRead $ do > withPreparedStatement (prepareQuery (sql q)) $ \pstmt -> do > withBoundStatement pstmt [] $ \bstmt -> do > rs <- doQuery bstmt iterMain [] > assertEqual "selectNestedMultiResultSet" [9,8,7,6,5,4,3,2,1] (map fst rs) > --print_ "" > selectNestedMultiResultSet3 :: OracleFunctions -> DBM mark Session () > selectNestedMultiResultSet3 _ = 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) [] > let expect = drop (9-outer) [8,7,6,5,4,3,2,1] > assertEqual "processOuter" expect (map fst rs) > result' ((outer,c):acc) > iterInner outer (inner::Int) (c::RefCursor StmtHandle) acc = do > rs <- doQuery c (iterInner2 outer inner) [] > let expect = drop (9-inner) [8,7,6,5,4,3,2,1] > assertEqual "processInner" expect rs > result' ((inner,c):acc) > iterInner2 outer inner (i::Int) acc = do > --print_ (show outer ++ " " ++ show inner ++ " " ++ show i) > assertBool "processInner2" (i < inner) > result' (i:acc) > withTransaction RepeatableRead $ do > rs <- doQuery (sql q) iterMain [] > assertEqual "selectNestedMultiResultSet" [9,8,7,6,5,4,3,2,1] (map fst rs) > --print_ "" > dropFixtureBindOutput = "DROP PROCEDURE takusenTestBindProc" > makeFixtureBindOutput = "CREATE or replace PROCEDURE takusenTestBindProc(x in out number, y in out varchar2)" > ++ " AS BEGIN\n" > ++ " y := 'output ' || y;\n" > ++ " x := x * 2;\n" > ++ " END;" > bindOutputString _ = do > let sqltext = "begin takusenTestBindProc(:1,:2); end;" > let qry = cmdbind sqltext [bindP (Out (1234::Int)), bindP (Out (Just "message"))] > (x, s) <- doQuery qry iter undefined > assertEqual "bindOutputString: int " 2468 x > assertEqual "bindOutputString: string " "output message" s > where > iter :: (Monad m) => Int -> String -> IterAct m (Int, String) > iter i s _ = return (Left (i, s)) > bindOutput _ = do > let sqltext = > "begin :1 := :1 + 1;\n " > ++ ":2 := :2 * 2;\n" > ++ "end;" > let qry = cmdbind sqltext [bindP (Out (44.4 :: Double)), bindP (Out (1234::Int))] > (d, i) <- doQuery qry iter undefined > assertEqual "bindOutput: double " 45.4 d > assertEqual "bindOutput: int " 2468 i > where > iter :: (Monad m) => Double -> Int -> IterAct m (Double, Int) > iter d i _ = return (Left (d, i)) > testList :: [OracleFunctions -> DBM mark Session ()] > testList = > [ selectNoRows, selectTerminatesEarly, selectFloatsAndInts > , selectNullString, selectEmptyString, selectUnhandledNull > , selectNullDate, selectDate, selectCalDate, selectBoundaryDates > , selectCursor, selectExhaustCursor > , selectBindString, selectBindInt, selectBindIntDoubleString > , selectBindDate, selectBindBoundaryDates, selectRebindStmt > , boundStmtDML, boundStmtDML2 > , polymorphicFetchTest, polymorphicFetchTestNull, exceptionRollback > , selectMultiResultSet, selectNestedMultiResultSet > , selectNestedMultiResultSet2, selectNestedMultiResultSet3 > , bindOutput, bindOutputString > ]