Database interface - would like advice on oracle library binding
oleg at pobox.com
oleg at pobox.com
Wed Sep 24 21:20:00 EDT 2003
The following code illustrates a _generic_ interface to low-level
database code. The left-fold iterator doQuery is completely generic
over any possible iterator -- no matter how many columns the query
returns, what are the types of these columns and what is the type of
the seed (accumulator). The code for doQuery remains the same. The
iterator allocates buffers for columns at the beginning and frees the
buffers at the very end. Again, this buffer handling is generic. There
is no longer need to write extraction/unmarshalling function for
specific types of rows. We only need fetching functions for specific
datatypes (not columns!). Again, the query and the row buffer
management code is completely generic. I guess I'm repeating
myself. The tests:
-- Query returns one column of type String
-- Never mind undefined: we return some static data in the buffers,
-- we don't have any oracle to bind to
test1 = doQuery undefined undefined iter1 ([]::[String])
where
iter1:: String -> [String] -> Either [String] [String]
iter1 s acc = Right $ s:acc
-- Query returns two columns of types String and Int
test2 = doQuery undefined undefined iter2 ([]::[(String,Int)])
where
iter2:: String -> Int -> [(String,Int)] ->
Either [(String,Int)] [(String,Int)]
iter2 s i acc = Right $ (s,i):acc
-- Query returns three columns of types Int, String and Int
test3 = doQuery undefined undefined iter3 ([]::[(Int,String,Int)])
where
iter3:: Int -> String -> Int -> [(Int,String,Int)] ->
Either [(Int,String,Int)] [(Int,String,Int)]
iter3 i1 s i2 acc = Right $ (i1,s,i2):acc
Use the function runtests to run either of these tests.
The code follows. Compiler flags:
-fglasgow-exts -fallow-overlapping-instances
-- DB column buffers
type BufferSize = Int
data BufferType = ORA_char | ORA_int
type Position = Int -- column number of the result table
data Buffer = Buffer { bufptr :: String -- for this stub, just use String
, nullindptr :: String -- likewise
, retsizeptr :: String -- likewise
, size:: BufferSize
, pos:: Position
, ora_type:: BufferType }
-- understandably, below is just a stub ...
alloc_buffer (siz, typ) ps =
return $ Buffer { bufptr = show ps, pos = ps, size = siz, ora_type = typ}
-- In this stub, don't do anything
free ptr = return ()
-- DB Column types
class DBType a where
alloc_buffer_hints:: a -> (BufferSize, BufferType)
col_fetch:: Buffer -> IO a
instance DBType String where
alloc_buffer_hints _ = (2000, ORA_char)
col_fetch buffer = return (bufptr buffer)
instance DBType Int where
alloc_buffer_hints _ = (4, ORA_int)
col_fetch buffer = return (read $ bufptr buffer)
-- need to add more ...
-- Row iteratees. Note, the folowing two instances cover ALL possible
-- iteratees. No other instances are needed
class SQLIteratee iter seed where
iter_apply:: [Buffer] -> seed -> iter -> IO (Either seed seed)
alloc_buffers:: Position -> iter -> seed -> IO [Buffer]
instance (DBType a) => SQLIteratee (a->seed->Either seed seed) seed where
iter_apply [buf] seed fn = col_fetch buf >>= (\v -> return$ fn v seed)
alloc_buffers n _ _ =
sequence [alloc_buffer (alloc_buffer_hints (undefined::a)) n]
instance (SQLIteratee iter' seed, DBType a) => SQLIteratee (a->iter') seed
where
iter_apply (buf:others) seed fn =
col_fetch buf >>= (\v -> iter_apply others seed (fn v))
alloc_buffers n fn seed = do
this_buffer <- alloc_buffer (alloc_buffer_hints (undefined::a)) n
other_buffers <- alloc_buffers (n+1) (fn (undefined::a)) seed
return (this_buffer:other_buffers)
free_buffers = mapM_ free
-- The left fold iterator -- the query executor
data Session -- not relevant for this example
data SQLStmt
db_execute session query = return ()
db_fetch_row buffers = return () -- use static data
doQuery:: (SQLIteratee iter seed) => Session -> SQLStmt -> iter -> seed -> IO seed
-- In this example, we just allocate buffers, "fetch" two rows and terminate
-- with a clean-up
doQuery session query iteratee seed = do
buffers <- alloc_buffers 0 iteratee seed
db_execute session query
db_fetch_row buffers
(Right seed) <- iter_apply buffers seed iteratee
db_fetch_row buffers
(Right seed) <- iter_apply buffers seed iteratee
free_buffers buffers
return seed
-- Tests
-- Query returns one column of type String
test1 = doQuery undefined undefined iter1 ([]::[String])
where
iter1:: String -> [String] -> Either [String] [String]
iter1 s acc = Right $ s:acc
-- Query returns two columns of types String and Int
test2 = doQuery undefined undefined iter2 ([]::[(String,Int)])
where
iter2:: String -> Int -> [(String,Int)] ->
Either [(String,Int)] [(String,Int)]
iter2 s i acc = Right $ (s,i):acc
-- Query returns three columns of types Int, String and Int
test3 = doQuery undefined undefined iter3 ([]::[(Int,String,Int)])
where
iter3:: Int -> String -> Int -> [(Int,String,Int)] ->
Either [(Int,String,Int)] [(Int,String,Int)]
iter3 i1 s i2 acc = Right $ (i1,s,i2):acc
runtests test = test >>= (mapM_ $ putStrLn . show)
More information about the Haskell-Cafe
mailing list