| Module : Database.Stub.Enumerator Copyright : (c) 2004 Oleg Kiselyov, Alistair Bayley License : BSD-style Maintainer : oleg@pobox.com, alistair@abayley.org Stability : experimental Portability : non-portable Stub implementation of Database.Enumerator. Useful for people who can't or won't install a DBMS, so that they can try out the Enumerator interface. Currently last last row of any fetch will have a null in its Int columns (this makes it easier to test handling of nulls and DBUnexpectedNull). See fetchIntVal. > {-# OPTIONS -fglasgow-exts #-} > {-# OPTIONS -fallow-undecidable-instances #-} > {-# OPTIONS -fallow-overlapping-instances #-} > module Database.Stub.Enumerator > -- Only the type constructor of Session is exported > -- (so the end user could write type signatures). > ( Session, ConnParm(..), connect, sql, prefetch > , QueryResourceUsage(..) > ) > where > import Database.InternalEnumerator > import Foreign > import Foreign.C > import Foreign.C.Types > import Control.Monad > import Control.Exception (catchDyn, throwDyn, throwIO) > import System.Time > import Data.IORef > import Data.Dynamic > data ConnParm = ConnParm{ user, pswd, dbname :: String } > data Session = Session > data StmtHandle = StmtHandle > data QueryString = QueryString String > data QueryStringTuned = QueryStringTuned QueryResourceUsage String data PreparedStatement = PreparedStatement { stmtSession :: Session, stmtHandle :: StmtHandle } > data Query = Query > { querySess :: Session > , queryStmt :: StmtHandle > , queryCounter :: IORef (IORef Int) > } > data DBColumnType = > DBTypeInt > | DBTypeString > | DBTypeDouble > | DBTypeDatetime > type BufferSize = Int |At present the only resource tuning we support is the number of rows prefetched by the FFI library. We use a record to (hopefully) make it easy to add other tuning parameters later. > data QueryResourceUsage = QueryResourceUsage { prefetchRowCount :: Int } > defaultResourceUsage :: QueryResourceUsage > defaultResourceUsage = QueryResourceUsage 100 -------------------------------------------------------------------- -- Sessions -------------------------------------------------------------------- > connect :: ConnParm -> ConnectA Session > connect connparm = ConnectA (return Session) > instance ISession Session where > disconnect sess = return () > beginTransaction sess isol = return () > commit sess = return () > rollback sess = return () -------------------------------------------------------------------- -- Statements -------------------------------------------------------------------- -- Simple statements: just a string > sql :: String -> QueryString > sql str = QueryString str > instance Command QueryString Session where > executeCommand s q = return 0 > instance Statement QueryString Session Query where > makeQuery sess stmt = do > -- Leave one counter in to ensure the fetch terminates > counter <- newIORef numberOfRowsToPretendToFetch > refc <- newIORef counter > return (Query sess StmtHandle refc) -- Statements with resource usage > prefetch :: Int -> String -> QueryStringTuned > prefetch count str = QueryStringTuned (QueryResourceUsage count) str > instance Command QueryStringTuned Session where > executeCommand s (QueryStringTuned _ str) = executeCommand s (QueryString str) > instance Statement QueryStringTuned Session Query where > -- Currently just ignore the tuning parameter. This is the stub > -- anyway. We only wish to test different types of statements > makeQuery s (QueryStringTuned _ str) = makeQuery s (QueryString str) -------------------------------------------------------------------- -- Queries -------------------------------------------------------------------- See makeQuery below for use of this: > numberOfRowsToPretendToFetch :: Int > numberOfRowsToPretendToFetch = 3 See fetchIntVal below for use of this: Note that rows are counted down from numberOfRowsToPretendToFetch, so this will throw on the last row. > throwNullIntOnRow :: Int > throwNullIntOnRow = 1 > instance IQuery Query Session ColumnBuffer > where > > fetchOneRow q = do > -- We'll pretend that we're going to fetch a finite number of rows. > refCounter <- readIORef (queryCounter q) > counter <- readIORef refCounter > if counter > 0 > then (modifyIORef refCounter pred >> return True) > else return False > > > currentRowNum q = do > refCounter <- readIORef (queryCounter q) > counter <- readIORef refCounter > return counter > > freeBuffer q buffer = return () > destroyQuery q = return () -------------------------------------------------------------------- -- result-set data buffers implementation -------------------------------------------------------------------- > data ColumnBuffer = ColumnBuffer > { colPos :: Int > } > buffer_pos q buffer = > do > let col = colPos buffer > row <- currentRowNum q > return (row,col) An auxiliary function: buffer allocation > allocBuffer q bufsize buftype colpos = do > return $ ColumnBuffer > { colPos = colpos > } > bufferToString :: ColumnBuffer -> IO (Maybe String) > bufferToString buffer = return $ Just "boo" > bufferToDatetime :: ColumnBuffer -> IO (Maybe CalendarTime) > bufferToDatetime colbuf = do > return $ Just $ CalendarTime > { ctYear = 1971 > , ctMonth = toEnum 6 > , ctDay = 1 > , ctHour = 12 > , ctMin = 1 > , ctSec = 1 > , ctPicosec = 0 > , ctWDay = Sunday > , ctYDay = -1 > , ctTZName = "UTC" > , ctTZ = 0 > , ctIsDST = False > } > bufferToInt :: ColumnBuffer -> IO (Maybe Int) > bufferToInt buffer = return $ Just 1 > bufferToDouble :: ColumnBuffer -> IO (Maybe Double) > bufferToDouble buffer = return $ Just 1.1 > {- > instance DBBind (Maybe a) SessionM PreparedStatement > => DBBind a SessionM PreparedStatement where > bindPos v q p = return () > instance DBBind (Maybe String) SessionM PreparedStatement where > bindPos v q p = return () > instance DBBind (Maybe Int) SessionM PreparedStatement where > bindPos v q p = return () > instance DBBind (Maybe Double) SessionM PreparedStatement where > bindPos v q p = return () > instance DBBind (Maybe CalendarTime) SessionM PreparedStatement where > bindPos v q p = return () > instance (Show a, Read a) => DBBind (Maybe a) SessionM PreparedStatement where > bindPos v q p = return () > -} > instance DBType (Maybe a) Query ColumnBuffer > => DBType a Query ColumnBuffer where > allocBufferFor _ = allocBufferFor (undefined::Maybe a) > fetchCol q buffer = throwIfDBNull (buffer_pos q buffer) $ fetchCol q buffer > instance DBType (Maybe String) Query ColumnBuffer where > allocBufferFor _ q n = allocBuffer q 4000 DBTypeString n > fetchCol q buffer = bufferToString buffer > instance DBType (Maybe Int) Query ColumnBuffer where > allocBufferFor _ q n = allocBuffer q 4 DBTypeInt n > fetchCol query buffer = do > refCounter <- readIORef (queryCounter query) > counter <- readIORef refCounter > -- last row returns null rather than 1 > if counter == throwNullIntOnRow > then return Nothing > else bufferToInt buffer > instance DBType (Maybe Double) Query ColumnBuffer where > allocBufferFor _ q n = allocBuffer q 8 DBTypeDouble n > fetchCol q buffer = bufferToDouble buffer > instance DBType (Maybe CalendarTime) Query ColumnBuffer where > allocBufferFor _ q n = allocBuffer q 8 DBTypeDatetime n > fetchCol q buffer = bufferToDatetime buffer A polymorphic instance which assumes that the value is in a String column, and uses Read to convert the String to a Haskell data value. > instance (Show a, Read a) => DBType (Maybe a) Query ColumnBuffer where > allocBufferFor _ = allocBufferFor (undefined::String) > fetchCol q buffer = do > v <- bufferToString buffer > case v of > Just s -> if s == "" then return Nothing else return (Just (read s)) > Nothing -> return Nothing