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
|
-- -*- mode: haskell; -*-
{-# CFILES hdbc-sqlite3-helper.c #-}
-- Above line for Hugs
module Database.HDBC.Sqlite3.Statement where
import Database.HDBC.Types
import Database.HDBC
import Database.HDBC.Sqlite3.Types
import Database.HDBC.Sqlite3.Utils
import Foreign.C.Types
import Foreign.ForeignPtr
import Foreign.Ptr
import Control.Concurrent.MVar
import Foreign.C.String
import Foreign.Marshal
import Foreign.Storable
import Control.Monad
import qualified Data.ByteString as B
import qualified Data.ByteString.UTF8 as BUTF8
import Data.List
import Control.Exception
import Database.HDBC.DriverUtils
#include <sqlite3.h>
{- One annoying thing about Sqlite is that a disconnect operation will actually
fail if there are any active statements. This is highly annoying, and makes
for some somewhat complex algorithms. -}
data StoState = Empty -- ^ Not initialized or last execute\/fetchrow had no results
| Prepared Stmt -- ^ Prepared but not executed
| Executed Stmt -- ^ Executed and more rows are expected
| Exhausted Stmt -- ^ Executed and at end of rows
instance Show StoState where
show Empty = "Empty"
show (Prepared _) = "Prepared"
show (Executed _) = "Executed"
show (Exhausted _) = "Exhausted"
data SState = SState {dbo :: Sqlite3,
stomv :: MVar StoState,
querys :: String,
colnamesmv :: MVar [String],
autoFinish :: Bool}
newSth :: Sqlite3 -> ChildList -> Bool -> String -> IO Statement
newSth indbo mchildren autoFinish str =
do newstomv <- newMVar Empty
newcolnamesmv <- newMVar []
let sstate = SState{dbo = indbo,
stomv = newstomv,
querys = str,
colnamesmv = newcolnamesmv,
autoFinish = autoFinish}
modifyMVar_ (stomv sstate) (\_ -> (fprepare sstate >>= return . Prepared))
let retval =
Statement {execute = fexecute sstate,
executeRaw = fexecuteRaw indbo str,
executeMany = fexecutemany sstate,
finish = public_ffinish sstate,
fetchRow = ffetchrow sstate,
originalQuery = str,
getColumnNames = readMVar (colnamesmv sstate),
describeResult = fail "Sqlite3 backend does not support describeResult"}
addChild mchildren retval
return retval
{- The deal with adding the \0 below is in response to an apparent bug in
sqlite3. See debian bug #343736.
This function assumes that any existing query in the state has already
been terminated. (FIXME: should check this at runtime.... never run fprepare
unless state is Empty)
-}
fprepare :: SState -> IO Stmt
fprepare sstate = withRawSqlite3 (dbo sstate)
(\p -> B.useAsCStringLen (BUTF8.fromString ((querys sstate) ++ "\0"))
(\(cs, cslen) -> alloca
(\(newp::Ptr (Ptr CStmt)) ->
(do res <- sqlite3_prepare p cs (fromIntegral cslen) newp nullPtr
checkError ("prepare " ++ (show cslen) ++ ": " ++ (querys sstate))
(dbo sstate) res
newo <- peek newp
newForeignPtr sqlite3_finalizeptr newo
)
)
)
)
{- General algorithm: find out how many columns we have, check the type
of each to see if it's NULL. If it's not, fetch it as text and return that.
Note that execute() will have already loaded up the first row -- and we
do that each time. so this function returns the row that is already in sqlite,
then loads the next row. -}
ffetchrow :: SState -> IO (Maybe [SqlValue])
ffetchrow sstate = modifyMVar (stomv sstate) dofetchrow
where dofetchrow Empty = return (Empty, Nothing)
dofetchrow (Prepared _) =
throwSqlError $ SqlError {seState = "HDBC Sqlite3 fetchrow",
seNativeError = (-1),
seErrorMsg = "Attempt to fetch row from Statement that has not been executed. Query was: " ++ (querys sstate)}
dofetchrow (Executed sto) = withStmt sto (\p ->
do ccount <- sqlite3_column_count p
-- fetch the data
res <- mapM (getCol p) [0..(ccount - 1)]
r <- fstep (dbo sstate) p
if r
then return (Executed sto, Just res)
else if (autoFinish sstate)
then do ffinish (dbo sstate) sto
return (Empty, Just res)
else return (Exhausted sto, Just res)
)
dofetchrow (Exhausted sto) = return (Exhausted sto, Nothing)
getCol p icol =
do t <- sqlite3_column_type p icol
if t == #{const SQLITE_NULL}
then return SqlNull
else do text <- sqlite3_column_text p icol
len <- sqlite3_column_bytes p icol
s <- B.packCStringLen (text, fromIntegral len)
case t of
#{const SQLITE_INTEGER} -> return $ SqlInt64 (read $ BUTF8.toString s)
#{const SQLITE_FLOAT} -> return $ SqlDouble (read $ BUTF8.toString s)
#{const SQLITE_BLOB} -> return $ SqlByteString s
#{const SQLITE_TEXT} -> return $ SqlByteString s
_ -> return $ SqlByteString s
fstep :: Sqlite3 -> Ptr CStmt -> IO Bool
fstep dbo p =
do r <- sqlite3_step p
case r of
#{const SQLITE_ROW} -> return True
#{const SQLITE_DONE} -> return False
#{const SQLITE_ERROR} -> checkError "step" dbo #{const SQLITE_ERROR}
>> (throwSqlError $ SqlError
{seState = "",
seNativeError = 0,
seErrorMsg = "In HDBC step, internal processing error (got SQLITE_ERROR with no error)"})
x -> checkError "step" dbo x
>> (throwSqlError $ SqlError
{seState = "",
seNativeError = fromIntegral x,
seErrorMsg = "In HDBC step, internal processing error (got error code with no error)"})
fexecute sstate args = modifyMVar (stomv sstate) doexecute
where doexecute (Executed sto) = doexecute (Prepared sto)
doexecute (Exhausted sto) = doexecute (Prepared sto)
doexecute Empty = -- already cleaned up from last time
do sto <- fprepare sstate
doexecute (Prepared sto)
doexecute (Prepared sto) = withStmt sto (\p ->
do c <- sqlite3_bind_parameter_count p
when (c /= genericLength args)
(throwSqlError $ SqlError {seState = "",
seNativeError = (-1),
seErrorMsg = "In HDBC execute, received " ++ (show args) ++ " but expected " ++ (show c) ++ " args."})
sqlite3_reset p >>= checkError "execute (reset)" (dbo sstate)
zipWithM_ (bindArgs p) [1..c] args
{- Logic for handling counts of changes: look at the total
changes before and after the query. If they differ,
then look at the local changes. (The local change counter
appears to not be updated unless really running a query
that makes a change, according to the docs.)
This is OK thread-wise because SQLite doesn't support
using a given dbh in more than one thread anyway. -}
origtc <- withSqlite3 (dbo sstate) sqlite3_total_changes
r <- fstep (dbo sstate) p
newtc <- withSqlite3 (dbo sstate) sqlite3_total_changes
changes <- if origtc == newtc
then return 0
else withSqlite3 (dbo sstate) sqlite3_changes
fgetcolnames p >>= swapMVar (colnamesmv sstate)
if r
then return (Executed sto, fromIntegral changes)
else if (autoFinish sstate)
then do ffinish (dbo sstate) sto
return (Empty, fromIntegral changes)
else return (Exhausted sto, fromIntegral changes)
)
bindArgs p i SqlNull =
sqlite3_bind_null p i >>=
checkError ("execute (binding NULL column " ++ (show i) ++ ")")
(dbo sstate)
bindArgs p i (SqlByteString bs) =
B.useAsCStringLen bs (bindCStringArgs p i)
bindArgs p i arg = bindArgs p i (SqlByteString (fromSql arg))
bindCStringArgs p i (cs, len) =
do r <- sqlite3_bind_text2 p i cs (fromIntegral len)
checkError ("execute (binding column " ++
(show i) ++ ")") (dbo sstate) r
fexecuteRaw :: Sqlite3 -> String -> IO ()
fexecuteRaw dbo query =
withSqlite3 dbo
(\p -> B.useAsCStringLen (BUTF8.fromString (query ++ "\0"))
(\(cs, cslen) -> do
result <- sqlite3_exec p cs nullFunPtr nullPtr nullPtr
case result of
#{const SQLITE_OK} -> return ()
s -> do
checkError "exec" dbo s
throwSqlError $ SqlError
{seState = "",
seNativeError = fromIntegral s,
seErrorMsg = "In sqlite3_exec, internal error"}
)
)
fgetcolnames csth =
do count <- sqlite3_column_count csth
mapM (getCol csth) [0..(count -1)]
where getCol csth i =
do cstr <- sqlite3_column_name csth i
bs <- B.packCString cstr
return (BUTF8.toString bs)
fexecutemany _ [] = return ()
fexecutemany sstate (args:[]) =
do fexecute sstate args
return ()
fexecutemany sstate (args:arglist) =
do fexecute (sstate { autoFinish = False }) args
fexecutemany sstate arglist
--ffinish o = withForeignPtr o (\p -> sqlite3_finalize p >>= checkError "finish")
-- Finish and change state
public_ffinish sstate = modifyMVar_ (stomv sstate) worker
where worker (Empty) = return Empty
worker (Prepared sto) = ffinish (dbo sstate) sto >> return Empty
worker (Executed sto) = ffinish (dbo sstate) sto >> return Empty
worker (Exhausted sto) = ffinish (dbo sstate) sto >> return Empty
ffinish dbo o = withRawStmt o (\p -> do r <- sqlite3_finalize p
checkError "finish" dbo r)
foreign import ccall unsafe "hdbc-sqlite3-helper.h &sqlite3_finalize_finalizer"
sqlite3_finalizeptr :: FunPtr ((Ptr CStmt) -> IO ())
foreign import ccall unsafe "hdbc-sqlite3-helper.h sqlite3_finalize_app"
sqlite3_finalize :: (Ptr CStmt) -> IO CInt
foreign import ccall unsafe "hdbc-sqlite3-helper.h sqlite3_prepare2"
sqlite3_prepare :: (Ptr CSqlite3) -> CString -> CInt -> Ptr (Ptr CStmt) -> Ptr (Ptr CString) -> IO CInt
foreign import ccall unsafe "sqlite3.h sqlite3_bind_parameter_count"
sqlite3_bind_parameter_count :: (Ptr CStmt) -> IO CInt
foreign import ccall unsafe "sqlite3.h sqlite3_step"
sqlite3_step :: (Ptr CStmt) -> IO CInt
foreign import ccall unsafe "sqlite3.h sqlite3_exec"
sqlite3_exec :: (Ptr CSqlite3)
-> CString
-> FunPtr (Ptr () -> CInt -> Ptr CString -> Ptr CString)
-> Ptr ()
-> Ptr CString
-> IO CInt
foreign import ccall unsafe "sqlite3.h sqlite3_reset"
sqlite3_reset :: (Ptr CStmt) -> IO CInt
foreign import ccall unsafe "sqlite3.h sqlite3_column_count"
sqlite3_column_count :: (Ptr CStmt) -> IO CInt
foreign import ccall unsafe "sqlite3.h sqlite3_column_name"
sqlite3_column_name :: Ptr CStmt -> CInt -> IO CString
foreign import ccall unsafe "sqlite3.h sqlite3_column_type"
sqlite3_column_type :: (Ptr CStmt) -> CInt -> IO CInt
foreign import ccall unsafe "sqlite3.h sqlite3_column_text"
sqlite3_column_text :: (Ptr CStmt) -> CInt -> IO CString
foreign import ccall unsafe "sqlite3.h sqlite3_column_bytes"
sqlite3_column_bytes :: (Ptr CStmt) -> CInt -> IO CInt
foreign import ccall unsafe "hdbc-sqlite3-helper.h sqlite3_bind_text2"
sqlite3_bind_text2 :: (Ptr CStmt) -> CInt -> CString -> CInt -> IO CInt
foreign import ccall unsafe "sqlite3.h sqlite3_bind_null"
sqlite3_bind_null :: (Ptr CStmt) -> CInt -> IO CInt
foreign import ccall unsafe "sqlite3.h sqlite3_changes"
sqlite3_changes :: Ptr CSqlite3 -> IO CInt
foreign import ccall unsafe "sqlite3.h sqlite3_total_changes"
sqlite3_total_changes :: Ptr CSqlite3 -> IO CInt
|