module Database.SQLite3.Direct (
open,
close,
errcode,
errmsg,
setTrace,
getAutoCommit,
setSharedCacheEnabled,
exec,
execWithCallback,
ExecCallback,
prepare,
getStatementDatabase,
step,
reset,
finalize,
clearBindings,
statementSql,
bindParameterCount,
bindParameterName,
bindParameterIndex,
columnCount,
columnName,
bindInt64,
bindDouble,
bindText,
bindBlob,
bindZeroBlob,
bindNull,
columnType,
columnInt64,
columnDouble,
columnText,
columnBlob,
setLoadExtensionEnabled,
lastInsertRowId,
changes,
totalChanges,
createFunction,
createAggregate,
deleteFunction,
funcArgCount,
funcArgType,
funcArgInt64,
funcArgDouble,
funcArgText,
funcArgBlob,
funcResultInt64,
funcResultDouble,
funcResultText,
funcResultBlob,
funcResultZeroBlob,
funcResultNull,
getFuncContextDatabase,
createCollation,
deleteCollation,
interrupt,
blobOpen,
blobClose,
blobReopen,
blobBytes,
blobRead,
blobReadBuf,
blobWrite,
backupInit,
backupFinish,
backupStep,
backupRemaining,
backupPagecount,
Database(..),
Statement(..),
ColumnType(..),
FuncContext(..),
FuncArgs(..),
Blob(..),
Backup(..),
StepResult(..),
BackupStepResult(..),
Error(..),
Utf8(..),
ParamIndex(..),
ColumnIndex(..),
ColumnCount,
ArgCount(..),
ArgIndex,
) where
import Database.SQLite3.Bindings
import qualified Data.ByteString as BS
import qualified Data.ByteString.Unsafe as BSU
import qualified Data.List.NonEmpty as NEL
import Data.Semigroup (Semigroup ((<>), sconcat))
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Control.Applicative ((<$>))
import Control.Exception as E
import Control.Monad (join, unless)
import Data.ByteString (ByteString)
import Data.IORef
import Data.Monoid (Monoid (mempty, mappend, mconcat))
import Data.String (IsString(..))
import Data.Text.Encoding.Error (lenientDecode)
import Foreign
import Foreign.C
import qualified System.IO.Unsafe as IOU
newtype Database = Database (Ptr CDatabase)
deriving (Eq, Show)
newtype Statement = Statement (Ptr CStatement)
deriving (Eq, Show)
data StepResult
= Row
| Done
deriving (Eq, Show)
data BackupStepResult
= BackupOK
| BackupDone
deriving (Eq, Show)
newtype Utf8 = Utf8 ByteString
deriving (Eq, Ord)
instance Show Utf8 where
show (Utf8 s) = (show . T.decodeUtf8With lenientDecode) s
instance IsString Utf8 where
fromString = Utf8 . T.encodeUtf8 . T.pack
instance Semigroup Utf8 where
Utf8 a <> Utf8 b = Utf8 (BS.append a b)
sconcat = Utf8 . BS.concat . NEL.toList . fmap (\(Utf8 s) -> s)
instance Monoid Utf8 where
mempty = Utf8 BS.empty
mappend = (<>)
mconcat = Utf8 . BS.concat . map (\(Utf8 s) -> s)
packUtf8 :: a -> (Utf8 -> a) -> CString -> IO a
packUtf8 n f cstr | cstr == nullPtr = return n
| otherwise = f . Utf8 <$> BS.packCString cstr
packCStringLen :: CString -> CNumBytes -> IO ByteString
packCStringLen cstr len =
BS.packCStringLen (cstr, fromIntegral len)
packUtf8Array :: IO a -> (Utf8 -> IO a) -> Int -> Ptr CString -> IO [a]
packUtf8Array onNull onUtf8 count base =
peekArray count base >>= mapM (join . packUtf8 onNull onUtf8)
unsafeUseAsCStringLenNoNull :: ByteString -> (CString -> CNumBytes -> IO a) -> IO a
unsafeUseAsCStringLenNoNull bs cb
| BS.null bs = cb (intPtrToPtr 1) 0
| otherwise = BSU.unsafeUseAsCStringLen bs $ \(ptr, len) ->
cb ptr (fromIntegral len)
wrapNullablePtr :: (Ptr a -> b) -> Ptr a -> Maybe b
wrapNullablePtr f ptr | ptr == nullPtr = Nothing
| otherwise = Just (f ptr)
type Result a = Either Error a
toResult :: a -> CError -> Result a
toResult a (CError 0) = Right a
toResult _ code = Left $ decodeError code
toResultM :: Monad m => m a -> CError -> m (Result a)
toResultM m (CError 0) = m >>= return . Right
toResultM _ code = return $ Left $ decodeError code
toStepResult :: CError -> Result StepResult
toStepResult code =
case decodeError code of
ErrorRow -> Right Row
ErrorDone -> Right Done
err -> Left err
toBackupStepResult :: CError -> Result BackupStepResult
toBackupStepResult code =
case decodeError code of
ErrorOK -> Right BackupOK
ErrorDone -> Right BackupDone
err -> Left err
newtype FuncContext = FuncContext (Ptr CContext)
deriving (Eq, Show)
data FuncArgs = FuncArgs CArgCount (Ptr (Ptr CValue))
data Blob = Blob Database (Ptr CBlob)
deriving (Eq, Show)
data Backup = Backup Database (Ptr CBackup)
deriving (Eq, Show)
open :: Utf8 -> IO (Either (Error, Utf8) Database)
open (Utf8 path) =
BS.useAsCString path $ \path' ->
alloca $ \database -> do
rc <- c_sqlite3_open path' database
db <- Database <$> peek database
case toResult () rc of
Left err -> do
msg <- errmsg db
_ <- close db
return $ Left (err, msg)
Right () ->
if db == Database nullPtr
then fail "sqlite3_open unexpectedly returned NULL"
else return $ Right db
close :: Database -> IO (Either Error ())
close (Database db) =
toResult () <$> c_sqlite3_close db
interrupt :: Database -> IO ()
interrupt (Database db) =
c_sqlite3_interrupt db
errcode :: Database -> IO Error
errcode (Database db) =
decodeError <$> c_sqlite3_errcode db
errmsg :: Database -> IO Utf8
errmsg (Database db) =
c_sqlite3_errmsg db >>= packUtf8 (Utf8 BS.empty) id
exec :: Database -> Utf8 -> IO (Either (Error, Utf8) ())
exec (Database db) (Utf8 sql) =
BS.useAsCString sql $ \sql' ->
alloca $ \msgPtrOut -> do
rc <- c_sqlite3_exec db sql' nullFunPtr nullPtr msgPtrOut
case toResult () rc of
Left err -> do
msgPtr <- peek msgPtrOut
msg <- packUtf8 (Utf8 BS.empty) id msgPtr
c_sqlite3_free msgPtr
return $ Left (err, msg)
Right () -> return $ Right ()
execWithCallback :: Database -> Utf8 -> ExecCallback -> IO (Either (Error, Utf8) ())
execWithCallback (Database db) (Utf8 sql) cb = do
abortReason <- newIORef Nothing :: IO (IORef (Maybe SomeException))
cbCache <- newIORef Nothing :: IO (IORef (Maybe ([Maybe Utf8] -> IO ())))
let getCallback cCount cNames = do
m <- readIORef cbCache
case m of
Nothing -> do
names <- packUtf8Array (fail "execWithCallback: NULL column name")
return
(fromIntegral cCount) cNames
let !cb' = cb (fromFFI cCount) names
writeIORef cbCache $ Just cb'
return cb'
Just cb' -> return cb'
let onExceptionAbort io =
(io >> return 0) `E.catch` \ex -> do
writeIORef abortReason $ Just ex
return 1
let cExecCallback _ctx cCount cValues cNames =
onExceptionAbort $ do
cb' <- getCallback cCount cNames
values <- packUtf8Array (return Nothing)
(return . Just)
(fromIntegral cCount) cValues
cb' values
BS.useAsCString sql $ \sql' ->
alloca $ \msgPtrOut ->
bracket (mkCExecCallback cExecCallback) freeHaskellFunPtr $
\pExecCallback -> do
let returnError err = do
msgPtr <- peek msgPtrOut
msg <- packUtf8 (Utf8 BS.empty) id msgPtr
c_sqlite3_free msgPtr
return $ Left (err, msg)
rc <- c_sqlite3_exec db sql' pExecCallback nullPtr msgPtrOut
case toResult () rc of
Left ErrorAbort -> do
m <- readIORef abortReason
case m of
Nothing -> returnError ErrorAbort
Just ex -> throwIO ex
Left err -> returnError err
Right () -> return $ Right ()
type ExecCallback
= ColumnCount
-> [Utf8]
-> [Maybe Utf8]
-> IO ()
setTrace :: Database -> Maybe (Utf8 -> IO ()) -> IO ()
setTrace (Database db) logger =
case logger of
Nothing -> do
_ <- c_sqlite3_trace db nullFunPtr nullPtr
return ()
Just output -> do
cb <- mkCTraceCallback $ \_ctx cStr -> do
msg <- packUtf8 (Utf8 BS.empty) id cStr
output msg
_ <- c_sqlite3_trace db cb nullPtr
return ()
getAutoCommit :: Database -> IO Bool
getAutoCommit (Database db) =
(/= 0) <$> c_sqlite3_get_autocommit db
setSharedCacheEnabled :: Bool -> IO (Either Error ())
setSharedCacheEnabled val =
toResult () <$> c_sqlite3_enable_shared_cache
(if val then 1 else 0)
prepare :: Database -> Utf8 -> IO (Either Error (Maybe Statement))
prepare (Database db) (Utf8 sql) =
BS.useAsCString sql $ \sql' ->
alloca $ \statement ->
c_sqlite3_prepare_v2 db sql' (1) statement nullPtr >>=
toResultM (wrapNullablePtr Statement <$> peek statement)
getStatementDatabase :: Statement -> IO Database
getStatementDatabase (Statement stmt) = do
db <- c_sqlite3_db_handle stmt
if db == nullPtr
then fail $ "sqlite3_db_handle(" ++ show stmt ++ ") returned NULL"
else return (Database db)
step :: Statement -> IO (Either Error StepResult)
step (Statement stmt) =
toStepResult <$> c_sqlite3_step stmt
reset :: Statement -> IO (Either Error ())
reset (Statement stmt) =
toResult () <$> c_sqlite3_reset stmt
finalize :: Statement -> IO (Either Error ())
finalize (Statement stmt) =
toResult () <$> c_sqlite3_finalize stmt
statementSql :: Statement -> IO (Maybe Utf8)
statementSql (Statement stmt) =
c_sqlite3_sql stmt >>= packUtf8 Nothing Just
clearBindings :: Statement -> IO ()
clearBindings (Statement stmt) = do
_ <- c_sqlite3_clear_bindings stmt
return ()
bindParameterCount :: Statement -> IO ParamIndex
bindParameterCount (Statement stmt) =
fromFFI <$> c_sqlite3_bind_parameter_count stmt
bindParameterName :: Statement -> ParamIndex -> IO (Maybe Utf8)
bindParameterName (Statement stmt) idx =
c_sqlite3_bind_parameter_name stmt (toFFI idx) >>=
packUtf8 Nothing Just
bindParameterIndex :: Statement -> Utf8 -> IO (Maybe ParamIndex)
bindParameterIndex (Statement stmt) (Utf8 name) =
BS.useAsCString name $ \name' -> do
idx <- fromFFI <$> c_sqlite3_bind_parameter_index stmt name'
return $ if idx == 0 then Nothing else Just idx
columnCount :: Statement -> IO ColumnCount
columnCount (Statement stmt) =
fromFFI <$> c_sqlite3_column_count stmt
columnName :: Statement -> ColumnIndex -> IO (Maybe Utf8)
columnName (Statement stmt) idx =
c_sqlite3_column_name stmt (toFFI idx) >>=
packUtf8 Nothing Just
bindInt64 :: Statement -> ParamIndex -> Int64 -> IO (Either Error ())
bindInt64 (Statement stmt) idx value =
toResult () <$> c_sqlite3_bind_int64 stmt (toFFI idx) value
bindDouble :: Statement -> ParamIndex -> Double -> IO (Either Error ())
bindDouble (Statement stmt) idx value =
toResult () <$> c_sqlite3_bind_double stmt (toFFI idx) value
bindText :: Statement -> ParamIndex -> Utf8 -> IO (Either Error ())
bindText (Statement stmt) idx (Utf8 value) =
unsafeUseAsCStringLenNoNull value $ \ptr len ->
toResult () <$>
c_sqlite3_bind_text stmt (toFFI idx) ptr len c_SQLITE_TRANSIENT
bindBlob :: Statement -> ParamIndex -> ByteString -> IO (Either Error ())
bindBlob (Statement stmt) idx value =
unsafeUseAsCStringLenNoNull value $ \ptr len ->
toResult () <$>
c_sqlite3_bind_blob stmt (toFFI idx) ptr len c_SQLITE_TRANSIENT
bindZeroBlob :: Statement -> ParamIndex -> Int -> IO (Either Error ())
bindZeroBlob (Statement stmt) idx len =
toResult () <$>
c_sqlite3_bind_zeroblob stmt (toFFI idx) (fromIntegral len)
bindNull :: Statement -> ParamIndex -> IO (Either Error ())
bindNull (Statement stmt) idx =
toResult () <$> c_sqlite3_bind_null stmt (toFFI idx)
columnType :: Statement -> ColumnIndex -> IO ColumnType
columnType (Statement stmt) idx =
decodeColumnType <$> c_sqlite3_column_type stmt (toFFI idx)
columnInt64 :: Statement -> ColumnIndex -> IO Int64
columnInt64 (Statement stmt) idx =
c_sqlite3_column_int64 stmt (toFFI idx)
columnDouble :: Statement -> ColumnIndex -> IO Double
columnDouble (Statement stmt) idx =
c_sqlite3_column_double stmt (toFFI idx)
columnText :: Statement -> ColumnIndex -> IO Utf8
columnText (Statement stmt) idx = do
ptr <- c_sqlite3_column_text stmt (toFFI idx)
len <- c_sqlite3_column_bytes stmt (toFFI idx)
Utf8 <$> packCStringLen ptr len
columnBlob :: Statement -> ColumnIndex -> IO ByteString
columnBlob (Statement stmt) idx = do
ptr <- c_sqlite3_column_blob stmt (toFFI idx)
len <- c_sqlite3_column_bytes stmt (toFFI idx)
packCStringLen ptr len
lastInsertRowId :: Database -> IO Int64
lastInsertRowId (Database db) =
c_sqlite3_last_insert_rowid db
changes :: Database -> IO Int
changes (Database db) =
fromIntegral <$> c_sqlite3_changes db
totalChanges :: Database -> IO Int
totalChanges (Database db) =
fromIntegral <$> c_sqlite3_total_changes db
data CFuncPtrs = CFuncPtrs (FunPtr CFunc) (FunPtr CFunc) (FunPtr CFuncFinal)
destroyCFuncPtrs :: FunPtr (CFuncDestroy ())
destroyCFuncPtrs = IOU.unsafePerformIO $ mkCFuncDestroy destroy
where
destroy p = do
let p' = castPtrToStablePtr p
CFuncPtrs p1 p2 p3 <- deRefStablePtr p'
unless (p1 == nullFunPtr) $ freeHaskellFunPtr p1
unless (p2 == nullFunPtr) $ freeHaskellFunPtr p2
unless (p3 == nullFunPtr) $ freeHaskellFunPtr p3
freeStablePtr p'
createFunction
:: Database
-> Utf8
-> Maybe ArgCount
-> Bool
-> (FuncContext -> FuncArgs -> IO ())
-> IO (Either Error ())
createFunction (Database db) (Utf8 name) nArgs isDet fun = mask_ $ do
funPtr <- mkCFunc fun'
u <- newStablePtr $ CFuncPtrs funPtr nullFunPtr nullFunPtr
BS.useAsCString name $ \namePtr ->
toResult () <$>
c_sqlite3_create_function_v2
db namePtr (maybeArgCount nArgs) flags (castStablePtrToPtr u)
funPtr nullFunPtr nullFunPtr destroyCFuncPtrs
where
flags = if isDet then c_SQLITE_DETERMINISTIC else 0
fun' ctx nArgs' cvals =
catchAsResultError ctx $
fun (FuncContext ctx) (FuncArgs nArgs' cvals)
createAggregate
:: Database
-> Utf8
-> Maybe ArgCount
-> a
-> (FuncContext -> FuncArgs -> a -> IO a)
-> (FuncContext -> a -> IO ())
-> IO (Either Error ())
createAggregate (Database db) (Utf8 name) nArgs initSt xStep xFinal = mask_ $ do
stepPtr <- mkCFunc xStep'
finalPtr <- mkCFuncFinal xFinal'
u <- newStablePtr $ CFuncPtrs nullFunPtr stepPtr finalPtr
BS.useAsCString name $ \namePtr ->
toResult () <$>
c_sqlite3_create_function_v2
db namePtr (maybeArgCount nArgs) 0 (castStablePtrToPtr u)
nullFunPtr stepPtr finalPtr destroyCFuncPtrs
where
xStep' ctx nArgs' cvals =
catchAsResultError ctx $ do
aggCtx <- getAggregateContext ctx
aggStPtr <- peek aggCtx
aggStRef <-
if castStablePtrToPtr aggStPtr /= nullPtr then
deRefStablePtr aggStPtr
else do
aggStRef <- newIORef initSt
aggStPtr' <- newStablePtr aggStRef
poke aggCtx aggStPtr'
return aggStRef
aggSt <- readIORef aggStRef
aggSt' <- xStep (FuncContext ctx) (FuncArgs nArgs' cvals) aggSt
writeIORef aggStRef aggSt'
xFinal' ctx = do
aggCtx <- getAggregateContext ctx
aggStPtr <- peek aggCtx
if castStablePtrToPtr aggStPtr == nullPtr then
catchAsResultError ctx $
xFinal (FuncContext ctx) initSt
else do
catchAsResultError ctx $ do
aggStRef <- deRefStablePtr aggStPtr
aggSt <- readIORef aggStRef
xFinal (FuncContext ctx) aggSt
freeStablePtr aggStPtr
getAggregateContext ctx =
c_sqlite3_aggregate_context ctx stPtrSize
stPtrSize = fromIntegral $ sizeOf (undefined :: StablePtr ())
catchAsResultError :: Ptr CContext -> IO () -> IO ()
catchAsResultError ctx action = E.catch action $ \exn -> do
let msg = show (exn :: SomeException)
withCAStringLen msg $ \(ptr, len) ->
c_sqlite3_result_error ctx ptr (fromIntegral len)
deleteFunction :: Database -> Utf8 -> Maybe ArgCount -> IO (Either Error ())
deleteFunction (Database db) (Utf8 name) nArgs =
BS.useAsCString name $ \namePtr ->
toResult () <$>
c_sqlite3_create_function_v2
db namePtr (maybeArgCount nArgs) 0 nullPtr
nullFunPtr nullFunPtr nullFunPtr nullFunPtr
maybeArgCount :: Maybe ArgCount -> CArgCount
maybeArgCount (Just n) = toFFI n
maybeArgCount Nothing = 1
funcArgCount :: FuncArgs -> ArgCount
funcArgCount (FuncArgs nArgs _) = fromIntegral nArgs
funcArgType :: FuncArgs -> ArgIndex -> IO ColumnType
funcArgType =
extractFuncArg NullColumn (fmap decodeColumnType . c_sqlite3_value_type)
funcArgInt64 :: FuncArgs -> ArgIndex -> IO Int64
funcArgInt64 = extractFuncArg 0 c_sqlite3_value_int64
funcArgDouble :: FuncArgs -> ArgIndex -> IO Double
funcArgDouble = extractFuncArg 0 c_sqlite3_value_double
funcArgText :: FuncArgs -> ArgIndex -> IO Utf8
funcArgText = extractFuncArg mempty $ \cval -> do
ptr <- c_sqlite3_value_text cval
len <- c_sqlite3_value_bytes cval
Utf8 <$> packCStringLen ptr len
funcArgBlob :: FuncArgs -> ArgIndex -> IO ByteString
funcArgBlob = extractFuncArg mempty $ \cval -> do
ptr <- c_sqlite3_value_blob cval
len <- c_sqlite3_value_bytes cval
packCStringLen ptr len
extractFuncArg :: a -> (Ptr CValue -> IO a) -> FuncArgs -> ArgIndex -> IO a
extractFuncArg defVal extract (FuncArgs nArgs p) idx
| 0 <= idx && idx < fromIntegral nArgs = do
cval <- peekElemOff p (fromIntegral idx)
extract cval
| otherwise = return defVal
funcResultInt64 :: FuncContext -> Int64 -> IO ()
funcResultInt64 (FuncContext ctx) value =
c_sqlite3_result_int64 ctx value
funcResultDouble :: FuncContext -> Double -> IO ()
funcResultDouble (FuncContext ctx) value =
c_sqlite3_result_double ctx value
funcResultText :: FuncContext -> Utf8 -> IO ()
funcResultText (FuncContext ctx) (Utf8 value) =
unsafeUseAsCStringLenNoNull value $ \ptr len ->
c_sqlite3_result_text ctx ptr len c_SQLITE_TRANSIENT
funcResultBlob :: FuncContext -> ByteString -> IO ()
funcResultBlob (FuncContext ctx) value =
unsafeUseAsCStringLenNoNull value $ \ptr len ->
c_sqlite3_result_blob ctx ptr len c_SQLITE_TRANSIENT
funcResultZeroBlob :: FuncContext -> Int -> IO ()
funcResultZeroBlob (FuncContext ctx) len =
c_sqlite3_result_zeroblob ctx (fromIntegral len)
funcResultNull :: FuncContext -> IO ()
funcResultNull (FuncContext ctx) =
c_sqlite3_result_null ctx
getFuncContextDatabase :: FuncContext -> IO Database
getFuncContextDatabase (FuncContext ctx) = do
db <- c_sqlite3_context_db_handle ctx
if db == nullPtr
then fail $ "sqlite3_context_db_handle(" ++ show ctx ++ ") returned NULL"
else return (Database db)
destroyCCompare :: CFuncDestroy ()
destroyCCompare ptr = freeHaskellFunPtr ptr'
where
ptr' = castPtrToFunPtr ptr :: FunPtr (CCompare ())
destroyCComparePtr :: FunPtr (CFuncDestroy ())
destroyCComparePtr = IOU.unsafePerformIO $ mkCFuncDestroy destroyCCompare
createCollation
:: Database
-> Utf8
-> (Utf8 -> Utf8 -> Ordering)
-> IO (Either Error ())
createCollation (Database db) (Utf8 name) cmp = mask_ $ do
cmpPtr <- mkCCompare cmp'
let u = castFunPtrToPtr cmpPtr
BS.useAsCString name $ \namePtr ->
toResult () <$> do
r <- c_sqlite3_create_collation_v2
db namePtr c_SQLITE_UTF8 u cmpPtr destroyCComparePtr
unless (r == CError 0) $
destroyCCompare $ castFunPtrToPtr cmpPtr
return r
where
cmp' _ len1 ptr1 len2 ptr2 = handle exnHandler $ do
s1 <- Utf8 <$> packCStringLen ptr1 len1
s2 <- Utf8 <$> packCStringLen ptr2 len2
let c = cmp s1 s2
evaluate (fromIntegral $ fromEnum c 1)
exnHandler (_ :: SomeException) = return (1)
deleteCollation :: Database -> Utf8 -> IO (Either Error ())
deleteCollation (Database db) (Utf8 name) =
BS.useAsCString name $ \namePtr ->
toResult () <$> do
c_sqlite3_create_collation_v2
db namePtr c_SQLITE_UTF8 nullPtr nullFunPtr nullFunPtr
setLoadExtensionEnabled :: Database -> Bool -> IO (Either Error ())
setLoadExtensionEnabled (Database db) enabled = do
toResult () <$> c_sqlite3_enable_load_extension db enabled
blobOpen
:: Database
-> Utf8
-> Utf8
-> Utf8
-> Int64
-> Bool
-> IO (Either Error Blob)
blobOpen (Database db) (Utf8 zDb) (Utf8 zTable) (Utf8 zColumn) rowid rw =
BS.useAsCString zDb $ \ptrDb ->
BS.useAsCString zTable $ \ptrTable ->
BS.useAsCString zColumn $ \ptrColumn ->
alloca $ \ptrBlob -> do
c_sqlite3_blob_open db ptrDb ptrTable ptrColumn rowid flags ptrBlob
>>= toResultM (Blob (Database db) <$> peek ptrBlob)
where
flags = if rw then 1 else 0
blobClose :: Blob -> IO (Either Error ())
blobClose (Blob _ blob) =
toResult () <$> c_sqlite3_blob_close blob
blobReopen :: Blob -> Int64 -> IO (Either Error ())
blobReopen (Blob _ blob) rowid =
toResult () <$> c_sqlite3_blob_reopen blob rowid
blobBytes :: Blob -> IO Int
blobBytes (Blob _ blob) =
fromIntegral <$> c_sqlite3_blob_bytes blob
blobRead
:: Blob
-> Int
-> Int
-> IO (Either Error ByteString)
blobRead blob len offset =
mask $ \restore -> do
buf <- mallocBytes len
r <- restore (blobReadBuf blob buf len offset)
`onException` (free buf)
case r of
Left err -> free buf >> return (Left err)
Right () -> do
bs <- BSU.unsafePackCStringFinalizer buf len (free buf)
return (Right bs)
blobReadBuf :: Blob -> Ptr a -> Int -> Int -> IO (Either Error ())
blobReadBuf (Blob _ blob) buf len offset =
toResult () <$>
c_sqlite3_blob_read blob buf (fromIntegral len) (fromIntegral offset)
blobWrite
:: Blob
-> ByteString
-> Int
-> IO (Either Error ())
blobWrite (Blob _ blob) bs offset =
BSU.unsafeUseAsCStringLen bs $ \(buf, len) ->
toResult () <$>
c_sqlite3_blob_write blob buf (fromIntegral len) (fromIntegral offset)
backupInit
:: Database
-> Utf8
-> Database
-> Utf8
-> IO (Either Error Backup)
backupInit (Database dstDb) (Utf8 dstName) (Database srcDb) (Utf8 srcName) =
BS.useAsCString dstName $ \dstName' ->
BS.useAsCString srcName $ \srcName' -> do
r <- c_sqlite3_backup_init dstDb dstName' srcDb srcName'
if r == nullPtr
then Left <$> errcode (Database dstDb)
else return (Right (Backup (Database dstDb) r))
backupFinish :: Backup -> IO (Either Error ())
backupFinish (Backup _ backup) =
toResult () <$>
c_sqlite3_backup_finish backup
backupStep :: Backup -> Int -> IO (Either Error BackupStepResult)
backupStep (Backup _ backup) pages =
toBackupStepResult <$>
c_sqlite3_backup_step backup (fromIntegral pages)
backupRemaining :: Backup -> IO Int
backupRemaining (Backup _ backup) =
fromIntegral <$> c_sqlite3_backup_remaining backup
backupPagecount :: Backup -> IO Int
backupPagecount (Backup _ backup) =
fromIntegral <$> c_sqlite3_backup_pagecount backup