hamsql-0.8.0.0: src/Database/HamSql/Internal/Stmt/Table.hs
-- This file is part of HamSql
--
-- Copyright 2014-2016 by it's authors.
-- Some rights reserved. See COPYING, AUTHORS.
{-# LANGUAGE FlexibleInstances #-}
module Database.HamSql.Internal.Stmt.Table
( stmtsDropTableConstr
, stmtsDropTableColumn
) where
import qualified Data.Text as T
import Database.HamSql.Internal.Stmt.Basic
import Database.HamSql.Internal.Stmt.Sequence()
-- | Assuming that CASCADE will only cause other constraints to be deleted.
-- | Required since foreign keys may depend on other keys.
stmtsDropTableConstr :: SqlObj SQL_TABLE_CONSTRAINT (SqlName, SqlName, SqlName)
-> [Maybe SqlStmt]
stmtsDropTableConstr x@(SqlObj _ (s, t, c)) =
[ newSqlStmt SqlDropTableConstr x $
"ALTER TABLE" <-> toSqlCode (s <.> t) <-> "DROP CONSTRAINT IF EXISTS" <->
toSqlCode c <->
"CASCADE"
]
stmtsDropTableColumn :: SqlObj SQL_COLUMN (SqlName, SqlName) -> [Maybe SqlStmt]
stmtsDropTableColumn x@(SqlObj _ (t, c)) =
[ newSqlStmt SqlDropTableColumn x $
"ALTER TABLE" <-> toSqlCode t <-> "DROP COLUMN" <-> toSqlCode c
]
constrId
:: Schema
-> Table
-> SqlName
-> SqlObj SQL_TABLE_CONSTRAINT (SqlName, SqlName, SqlName)
constrId s t c = SqlObj SQL_TABLE_CONSTRAINT (schemaName s, tableName t, c)
-- TODO: prefix with table name
stmtCheck
:: ToSqlId a
=> a -> Check -> Maybe SqlStmt
stmtCheck obj c =
newSqlStmt SqlCreateCheckConstr obj $
"ALTER TABLE " <> sqlIdCode obj <> " ADD CONSTRAINT " <>
toSqlCode (checkName c) <>
" CHECK (" <>
checkCheck c <>
")"
instance ToSqlStmts (SqlContext (Schema, Table, Column)) where
toSqlStmts context obj@(SqlContext (schema, table, rawColumn)) =
[ stmtAddColumn
, stmtAlterColumnType
, stmtDropDefault
, stmtAddColumnDefault
, stmtAlterColumnNull
, stmtCommentOn obj (columnDescription c)
, stmtAddForeignKey
, stmtColumnUnique
] ++
stmtsSerialSequence ++ stmtsAddColumnCheck
-- ADD COLUMN
where
stmtAddColumn =
newSqlStmt SqlAddColumn obj $
"ALTER TABLE" <-> tblId <-> "ADD COLUMN" <-> toSqlCode (columnName c) <->
toSqlCode (columnType c)
-- UNIQUE
stmtColumnUnique
| columnUnique c == Just True =
let constr = tableName table <> columnName c <> SqlName "key"
in newSqlStmt SqlCreateUniqueConstr (constrId schema table constr) $
"ALTER TABLE " <> tblId <> " ADD CONSTRAINT " <> toSqlCode constr <>
" UNIQUE (" <>
toSqlCode (columnName c) <>
")"
| otherwise = Nothing
-- NOT NULL
stmtAlterColumnNull =
stmtAlterColumn SqlAlterColumn $
if columnNull c == Just True
then "DROP NOT NULL"
else "SET NOT NULL"
-- SET DATA TYPE
stmtAlterColumnType =
stmtAlterColumn SqlAlterColumn $
"SET DATA TYPE " <> toSqlCode (columnType c)
-- DROP DEFAULT
stmtDropDefault = stmtAlterColumn SqlDropColumnDefault "DROP DEFAULT"
-- SET DEFAULT
stmtAddColumnDefault = columnDefault c >>= sqlDefault
where
sqlDefault d = stmtAlterColumn SqlAddDefault $ "SET DEFAULT " <> d
-- [CHECK]
stmtsAddColumnCheck = maybeMap (stmtCheck obj) (columnChecks c)
-- FOREIGN KEY
stmtAddForeignKey =
case columnReferences c of
Nothing -> Nothing
(Just ref) ->
let constr = tableName table <> columnName c <> SqlName "fkey"
in newSqlStmt
SqlCreateForeignKeyConstr
(constrId schema table constr) $
"ALTER TABLE" <-> sqlIdCode obj <-> "ADD CONSTRAINT" <->
toSqlCode constr <->
"FOREIGN KEY (" <>
toSqlCode (columnName c) <>
")" <->
"REFERENCES" <->
toSqlCode' (init $ expSqlName ref) <->
"(" <>
toSqlCode (last $ expSqlName ref) <>
")" <>
maybePrefix " ON UPDATE " (columnOnRefUpdate c) <>
maybePrefix " ON DELETE " (columnOnRefDelete c)
-- CREATE SEQUENCE (for type SERIAL)
stmtsSerialSequence
| columnIsSerial = toSqlStmts context serialSequenceContext
| otherwise = [Nothing]
-- Helpers
stmtAlterColumn t x =
newSqlStmt t obj $
"ALTER TABLE " <> tblId <> " ALTER COLUMN " <> toSqlCode (columnName c) <>
" " <>
x
columnIsSerial = toSqlCode (columnType rawColumn) == "SERIAL"
c
| columnIsSerial =
rawColumn
{ columnType = SqlType "integer"
, columnDefault =
Just $
"nextval('" <> toSqlCode (sqlId serialSequenceContext) <> "')"
}
| otherwise = rawColumn
tblId = toSqlCode $ schemaName schema <.> tableName table
serialSequenceContext =
SqlContext
( schema
, Sequence
-- sequenceName follows PostgreSQL internal convention
{ sequenceName = tableName table <> columnName c <> SqlName "_seq"
, sequenceIncrement = Nothing
, sequenceMinValue = Nothing
, sequenceMaxValue = Nothing
, sequenceStartValue = Nothing
, sequenceCache = Nothing
, sequenceCycle = Nothing
, sequenceOwnedByColumn = Just $ SqlName $ sqlIdCode obj
})
instance ToSqlStmts (SqlContext (Schema, Table)) where
toSqlStmts SetupContext {setupContextSetup = setup} obj@(SqlContext (s, t)) =
[ stmtCreateTable
-- table comment
, stmtCommentOn obj (tableDescription t)
] ++
maybeMap (stmtCheck obj) (tableChecks t) ++
-- grant rights to roles
maybeMap (sqlGrant "SELECT") (tablePrivSelect t) ++
maybeMap (sqlGrant "UPDATE") (tablePrivUpdate t) ++
maybeMap (sqlGrant "INSERT") (tablePrivInsert t) ++
maybeMap (sqlGrant "DELETE") (tablePrivDelete t) ++
-- primary key
[sqlAddPrimaryKey (tablePrimaryKey t)] ++
-- mult column unique
maybeMap sqlUniqueConstr (tableUnique t) ++
-- inheritance
maybeMap sqlAddInheritance (tableInherits t) ++
-- multi column FKs
maybeMap sqlAddForeignKey' (tableForeignKeys t)
where
stmtCreateTable =
newSqlStmt SqlCreateTable obj $
"CREATE TABLE IF NOT EXISTS" <-> sqlIdCode obj <> " ()"
-- PRIMARY KEY
sqlAddPrimaryKey :: [SqlName] -> Maybe SqlStmt
sqlAddPrimaryKey [] = Nothing
sqlAddPrimaryKey ks =
let constr = tableName t <> SqlName "pkey"
in newSqlStmt SqlCreatePrimaryKeyConstr (constrId s t constr) $
"ALTER TABLE " <> sqlIdCode obj <> " ADD CONSTRAINT " <> toSqlCode constr <>
" PRIMARY KEY (" <>
T.intercalate ", " (map toSqlCode ks) <>
")"
-- TODO: allow empty name with "mconcat (uniquekeyColumns ks)"
sqlUniqueConstr :: UniqueKey -> Maybe SqlStmt
sqlUniqueConstr ks =
let constr = tableName t <> uniquekeyName ks
in newSqlStmt SqlCreateUniqueConstr (constrId s t constr) $
"ALTER TABLE " <> sqlIdCode obj <> " ADD CONSTRAINT " <> toSqlCode constr <>
" UNIQUE (" <>
T.intercalate ", " (map toSqlCode (uniquekeyColumns ks)) <>
")"
sqlAddForeignKey' :: ForeignKey -> Maybe SqlStmt
sqlAddForeignKey' fk =
let constr = tableName t <> foreignkeyName fk
in newSqlStmt SqlCreateForeignKeyConstr (constrId s t constr) $
"ALTER TABLE " <> sqlIdCode obj <> " ADD CONSTRAINT " <> toSqlCode constr <>
" FOREIGN KEY (" <>
T.intercalate ", " (map toSqlCode (foreignkeyColumns fk)) <>
")" <>
" REFERENCES " <>
toSqlCode (foreignkeyRefTable fk) <>
" (" <>
T.intercalate ", " (map toSqlCode $ foreignkeyRefColumns fk) <>
")" <>
maybePrefix " ON UPDATE " (foreignkeyOnUpdate fk) <>
maybePrefix " ON DELETE " (foreignkeyOnDelete fk)
sqlGrant right role =
newSqlStmt
SqlPriv
obj
("GRANT " <> right <> " ON TABLE " <> toSqlCode (tableName t) <>
" TO " <>
prefixedRole setup role)
sqlAddInheritance :: SqlName -> Maybe SqlStmt
sqlAddInheritance n =
newSqlStmt SqlAlterTable obj $
"ALTER TABLE " <> sqlIdCode obj <> " INHERIT " <> toSqlCode n