packages feed

qhs-0.3.2: src/SQL.hs

module SQL (open, close, createTable, insertRow, execute) where

import Control.Exception (try, SomeException)
import Control.Monad (forM)
import Data.List (intersperse)
import Data.String (fromString)
import qualified Data.Text as T
import qualified Database.SQLite.Simple as SQLite

import SQLType

-- | Open a new database connection.
open :: String -> IO SQLite.Connection
open = SQLite.open

-- | Close a database connection.
close :: SQLite.Connection -> IO ()
close = SQLite.close

-- | Creates a new table.
createTable :: SQLite.Connection -> String -> [String] -> [SQLType] -> IO (Maybe String)
createTable conn name columns types = do
  let stmt = "CREATE TABLE " ++ sqlQuote name ++ " "
           ++ tupled (map quote (zip types columns)) ++ ";"
  e <- try $ SQLite.execute_ conn (fromString stmt)
  return $ either (Just . (show :: SomeException -> String)) (const Nothing) e
    where quote (t, c) = sqlQuote c ++ " " ++ showType t

-- | Inserts a row into a table.
insertRow :: SQLite.Connection -> String -> [String] -> [SQLType] -> [String] -> IO (Maybe String)
insertRow conn name columns types entry = do
  let stmt = "INSERT INTO " ++ sqlQuote name ++ tupled (map sqlQuote columns)
           ++ " VALUES " ++ tupled (map quote (zip types entry)) ++ ";"
  e <- try $ SQLite.execute_ conn (fromString stmt)
  return $ either (Just . (show :: SomeException -> String)) (const Nothing) e
    where quote (t, "") | isDigitType t = "NULL"
          quote (t, cs) | isDigitType t = cs
                        | otherwise = "'" ++ toSQLString cs ++ "'"
          isDigitType SQLInt = True
          isDigitType _ = False

toSQLString :: String -> String
toSQLString "" = ""
toSQLString ('\'':xs) = '\'':'\'':toSQLString xs
toSQLString (x:xs) = x : toSQLString xs

-- | Executes a SQL statement.
execute :: SQLite.Connection -> String -> IO (Either String ([String], [[Any]]))
execute conn query = do
  e <- try $ SQLite.query_ conn (fromString query)
  columns <- SQLite.withStatement conn (fromString query) $ \stmt -> do
    cnt <- toInteger <$> SQLite.columnCount stmt
    forM [0..cnt-1] $ \i ->
      T.unpack <$> SQLite.columnName stmt (fromInteger i)
  return $ either (Left . (show :: SomeException -> String)) (Right . (,) columns) $ e

sqlQuote :: String -> String
sqlQuote xs = "`" ++ xs ++ "`"

tupled :: [String] -> String
tupled xs = "(" ++ concat (intersperse ", " xs) ++ ")"