selda-0.1.1.0: src/Database/Selda/Table/Compile.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Generating SQL for creating and deleting tables.
module Database.Selda.Table.Compile where
import Database.Selda.Table
import Data.List (foldl')
import Data.Monoid
import Data.Text (Text, intercalate, pack)
import qualified Data.Text as Text
data OnError = Fail | Ignore
deriving (Eq, Ord, Show)
-- | Compile a @CREATE TABLE@ query from a table definition.
compileCreateTable :: (Text -> [ColAttr] -> Maybe Text) -> OnError -> Table a -> Text
compileCreateTable customColType ifex tbl = mconcat
[ "CREATE TABLE ", ifNotExists ifex, tableName tbl, "("
, intercalate ", " (map (compileTableCol customColType) (tableCols tbl))
, ")"
]
where
ifNotExists Fail = ""
ifNotExists Ignore = "IF NOT EXISTS "
-- | Compile a table column.
compileTableCol :: (Text -> [ColAttr] -> Maybe Text) -> ColInfo -> Text
compileTableCol customColType ci = Text.unwords
[ colName ci
, case customColType typ attrs of
Just s -> s
_ -> typ <> " " <> Text.unwords (map compileColAttr attrs)
]
where
typ = colType ci
attrs = colAttrs ci
-- | Compile a @DROP TABLE@ query.
compileDropTable :: OnError -> Table a -> Text
compileDropTable Fail t = Text.unwords ["DROP TABLE",tableName t]
compileDropTable _ t = Text.unwords ["DROP TABLE IF EXISTS",tableName t]
-- | Compile an @INSERT INTO@ query inserting @m@ rows with @n@ cols each.
-- Note that backends expect insertions to NOT have a semicolon at the end.
compInsert :: Text -> Table a -> [[Bool]] -> Text
compInsert defaultKeyword tbl defs =
Text.unwords ["INSERT INTO", tableName tbl, names, "VALUES", values]
where
colNames = map colName $ tableCols tbl
names = "(" <> Text.intercalate ", " colNames <> ")"
values = Text.intercalate ", " (mkRows (1 :: Int) defs)
-- Build all rows: just recurse over the list of defaults (which encodes
-- the # of elements in total as well), building each row, keeping track
-- of the next parameter identifier.
mkRows n (ds:dss) =
case mkRow n ds (tableCols tbl) of
(n', vals) -> mkRowText (reverse vals) : mkRows n' dss
mkRows _ _ =
[]
mkRowText vals = "(" <> Text.intercalate ", " vals <> ")"
-- Build a row: use the NULL/DEFAULT keyword for default rows, otherwise
-- use a parameter.
mkRow n ds cs = foldl' mkCols (n, []) (zip ds cs)
-- Build a column: default values only available for for auto-incrementing
-- primary keys.
mkCol n def col
| def && not (AutoIncrement `elem` colAttrs col) =
error "only auto-incrementing primary keys may have defaults"
| def =
(n, defaultKeyword)
| otherwise =
(n+1, pack ('$':show n))
-- Create a colum and return the next parameter id, plus the column itself.
mkCols (n, cols) (def, col) =
fmap (:cols) (mkCol n def col)
-- | Compile a column attribute.
compileColAttr :: ColAttr -> Text
compileColAttr Primary = "PRIMARY KEY"
compileColAttr AutoIncrement = "AUTOINCREMENT"
compileColAttr Required = "NOT NULL"
compileColAttr Optional = "NULL"