File: Statement.hsc

package info (click to toggle)
hdbc-sqlite3 2.3.3.1-11
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 180 kB
  • sloc: haskell: 701; ansic: 146; makefile: 32
file content (294 lines) | stat: -rw-r--r-- 13,181 bytes parent folder | download | duplicates (5)
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