packages feed

hamsql-0.7.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 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 :: SqlIdContentSqoObj -> [Maybe SqlStmt]
stmtsDropTableConstr x =
  [ newSqlStmt SqlDropTableConstr x $
    "ALTER TABLE" <-> sqlSqoIdCode x <-> "DROP CONSTRAINT IF EXISTS" <->
    sqlSqoObjIdCode x <->
    "CASCADE"
  ]

stmtsDropTableColumn :: SqlIdContentSqoObj -> [Maybe SqlStmt]
stmtsDropTableColumn x =
  [ newSqlStmt SqlDropTableColumn x $
    "ALTER TABLE" <-> sqlSqoIdCode x <-> "DROP COLUMN" <-> sqlSqoObjIdCode x
  ]

columnObj :: SqlContextSqo Table -> Column -> SqlContextSqoObj Table Column
columnObj x y =
  SqlContextSqoObj
  { sqlSqoObjectSchema = sqlSqoSchema x
  , sqlSqoObject1 = sqlSqoObject x
  , sqlSqoObject2 = y
  }

instance ToSqlStmts (SqlContextSqoObj Table Column) where
  toSqlStmts _ _ = []

instance ToSqlStmts (SqlContextSqo Table) where
  toSqlStmts = stmtsDeployTable

stmtsDeployTable :: SetupContext -> SqlContextSqo Table -> [Maybe SqlStmt]
stmtsDeployTable context@SetupContext {setupContextSetup = setup} obj@SqlContextSqo {sqlSqoSchema = m
                                                                                    ,sqlSqoObject = t}
                                                                  -- table with columns
 =
  [ stmtCreateTable
    -- table comment
  , stmtCommentOn obj (tableDescription t)
  ] ++
  map stmtAddColumn columns ++
  map stmtAlterColumnType columns ++
  map stmtDropDefault columns ++
  map stmtAddColumnDefault columns ++
  map stmtAlterColumnNull columns ++
  concatMap stmtAddColumnCheck columns ++
  concat (sequences (tableColumns t)) ++
  maybeMap stmtCheck (tableChecks t) ++
  -- column comments
  map (\c -> stmtCommentOn (columnObj obj c) (columnDescription c)) columns ++
  -- 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) ++
  -- single column FKs (references)
  map sqlAddForeignKey columns ++
  -- inheritance
  maybeMap sqlAddInheritance (tableInherits t) ++
  -- multi column unique constraints
  map sqlColumnUnique columns ++
  -- multi column FKs
  maybeMap sqlAddForeignKey' (tableForeignKeys t)
  where
    stmtCreateTable =
      newSqlStmt SqlCreateTable obj $
      "CREATE TABLE IF NOT EXISTS" <-> sqlIdCode obj <> " ()"
    stmtCheck c =
      newSqlStmt SqlCreateCheckConstr obj $
      "ALTER TABLE " <> sqlIdCode obj <> " ADD CONSTRAINT " <>
      constrName (checkName c) <>
      " CHECK (" <>
      checkCheck c <>
      ")"
    -- COLUMNS
    sqlAlterColumn c =
      "ALTER TABLE " <> sqlIdCode obj <> " ALTER COLUMN " <>
      toSqlCode (columnName c) <>
      " "
    stmtAddColumn c =
      newSqlStmt SqlAddColumn (columnObj obj c) $
      "ALTER TABLE" <-> sqlIdCode obj <-> "ADD COLUMN" <->
      toSqlCode (columnName c) <->
      toSqlCode (columnType c)
    stmtAlterColumnType c =
      newSqlStmt SqlAlterColumn obj $
      sqlAlterColumn c <> "SET DATA TYPE " <> toSqlCode (columnType c)
    stmtDropDefault c =
      newSqlStmt SqlDropColumnDefault obj $ sqlAlterColumn c <> "DROP DEFAULT"
    stmtAddColumnCheck c = maybeMap stmtCheck (columnChecks c)
    stmtAlterColumnNull c =
      newSqlStmt SqlAlterColumn obj $
      sqlAlterColumn c <> sqlSetNull (columnNull c)
      where
        sqlSetNull Nothing = sqlSetNull (Just False)
        sqlSetNull (Just False) = "SET NOT NULL"
        sqlSetNull (Just True) = "DROP NOT NULL"
    stmtAddColumnDefault c = columnDefault c >>= sqlDefault
      where
        sqlDefault d =
          newSqlStmt SqlAddDefault (columnObj obj c) $
          sqlAlterColumn c <> "SET DEFAULT " <> d
    -- SERIAL
    columns = map injectSerialParameters (tableColumns t)
    injectSerialParameters c
      | columnIsSerial c =
        c
        { columnType = SqlType "integer"
        , columnDefault =
          Just $ "nextval('" <> sqlIdCode (serialSqlContext c) <> "')"
        }
      | otherwise = c
    columnIsSerial c = toSqlCode (columnType c) == "SERIAL"
    -- do not change this, it is PostgreSQL internal convention
    serialSequenceName c =
      tableName t // SqlName "_" // columnName c // SqlName "_seq"
    sequences cs = map serial (filter columnIsSerial cs)
      where
        serial c = stmtsDeploySequence context (serialSqlContext c)
    serialSqlContext c =
      SqlContextSqo
      { sqlSqoSchema = m
      , sqlSqoObject =
        Sequence
        { sequenceName = serialSequenceName c
        , sequenceIncrement = Nothing
        , sequenceMinValue = Nothing
        , sequenceMaxValue = Nothing
        , sequenceStartValue = Nothing
        , sequenceCache = Nothing
        , sequenceCycle = Nothing
        , sequenceOwnedByColumn = Just $ SqlName $ sqlIdCode (columnObj obj c)
        }
      }
    -- PRIMARY KEY
    sqlAddPrimaryKey :: [SqlName] -> Maybe SqlStmt
    sqlAddPrimaryKey [] = Nothing
    sqlAddPrimaryKey ks =
      newSqlStmt SqlCreatePrimaryKeyConstr (constrId (SqlName "primary_key")) $
      "ALTER TABLE " <> sqlIdCode obj <> " ADD CONSTRAINT " <>
      constrName (SqlName "primary_key") <>
      " PRIMARY KEY (" <>
      T.intercalate ", " (map toSqlCode ks) <>
      ")"
    sqlUniqueConstr :: UniqueKey -> Maybe SqlStmt
    sqlUniqueConstr ks =
      newSqlStmt SqlCreateUniqueConstr (constrId (uniquekeyName ks)) $
      "ALTER TABLE " <> sqlIdCode obj <> " ADD CONSTRAINT " <>
      constrName (uniquekeyName ks) <>
      " UNIQUE (" <>
      T.intercalate ", " (map toSqlCode (uniquekeyColumns ks)) <>
      ")"
    --sqlCheck c =
    --    " CONSTRAINT " <> name (checkName c) <> " CHECK (" <> checkCheck c <> ")"
    sqlAddForeignKey :: Column -> Maybe SqlStmt
    sqlAddForeignKey Column {columnReferences = Nothing} = Nothing
    sqlAddForeignKey c@Column {columnReferences = (Just ref)} =
      newSqlStmt SqlCreateForeignKeyConstr (constrId (columnName c)) $
      "ALTER TABLE " <> sqlIdCode obj <> " ADD CONSTRAINT " <>
      constrName (columnName c) <>
      " FOREIGN KEY (" <>
      toSqlCode (columnName c) <>
      ")" <>
      " REFERENCES " <>
      toSqlCode' (init $ expSqlName ref) <>
      " (" <>
      toSqlCode (last $ expSqlName ref) <>
      ")" <>
      sqlOnRefUpdate (columnOnRefUpdate c) <>
      sqlOnRefDelete (columnOnRefDelete c)
    sqlAddForeignKey' :: ForeignKey -> Maybe SqlStmt
    sqlAddForeignKey' fk =
      newSqlStmt SqlCreateForeignKeyConstr (constrId (foreignkeyName fk)) $
      "ALTER TABLE " <> sqlIdCode obj <> " ADD CONSTRAINT " <>
      constrName (foreignkeyName fk) <>
      " FOREIGN KEY (" <>
      T.intercalate ", " (map toSqlCode (foreignkeyColumns fk)) <>
      ")" <>
      " REFERENCES " <>
      toSqlCode (foreignkeyRefTable fk) <>
      " (" <>
      T.intercalate ", " (map toSqlCode $ foreignkeyRefColumns fk) <>
      ")" <>
      sqlOnRefUpdate (foreignkeyOnUpdate fk) <>
      sqlOnRefDelete (foreignkeyOnDelete fk)
    sqlOnRefUpdate Nothing = ""
    sqlOnRefUpdate (Just a) = " ON UPDATE " <> a
    sqlOnRefDelete Nothing = ""
    sqlOnRefDelete (Just a) = " ON DELETE " <> a
    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
    sqlColumnUnique c@Column {columnUnique = (Just True)} =
      newSqlStmt SqlCreateUniqueConstr (constrId (columnName c)) $
      "ALTER TABLE " <> sqlIdCode obj <> " ADD CONSTRAINT " <>
      constrName (columnName c) <>
      " UNIQUE (" <>
      toSqlCode (columnName c) <>
      ")"
    sqlColumnUnique _ = Nothing
    -- tools
    constrName a = toSqlCode (tableName t // SqlName "-" // a)
    constrId a =
      SqlIdContentSqoObj
        "TABLE-CONSTRAINT"
        (SqlName $ sqlIdCode obj)
        (SqlName $ constrName a)