packages feed

orville-postgresql-1.0.0.0: src/Orville/PostgreSQL/Expr/TableDefinition.hs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

{- |
Copyright : Flipstone Technology Partners 2023
License   : MIT
Stability : Stable

@since 1.0.0.0
-}
module Orville.PostgreSQL.Expr.TableDefinition
  ( CreateTableExpr
  , createTableExpr
  , PrimaryKeyExpr
  , primaryKeyExpr
  , AlterTableExpr
  , alterTableExpr
  , AlterTableAction
  , addColumn
  , dropColumn
  , addConstraint
  , dropConstraint
  , alterColumnType
  , alterColumnSetDefault
  , alterColumnDropDefault
  , UsingClause
  , usingCast
  , alterColumnNullability
  , AlterNotNull
  , setNotNull
  , dropNotNull
  , DropTableExpr
  , dropTableExpr
  )
where

import Data.List.NonEmpty (NonEmpty)
import Data.Maybe (catMaybes, maybeToList)

import Orville.PostgreSQL.Expr.ColumnDefinition (ColumnDefinition)
import Orville.PostgreSQL.Expr.DataType (DataType)
import Orville.PostgreSQL.Expr.IfExists (IfExists)
import Orville.PostgreSQL.Expr.Name (ColumnName, ConstraintName, Qualified, TableName)
import Orville.PostgreSQL.Expr.TableConstraint (TableConstraint)
import qualified Orville.PostgreSQL.Raw.RawSql as RawSql

{- |
Type to represent a @CREATE TABLE@ statement. E.G.

> CREATE TABLE foo (id integer)

'CreateTableExpr' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.

@since 1.0.0.0
-}
newtype CreateTableExpr
  = CreateTableExpr RawSql.RawSql
  deriving (RawSql.SqlExpression)

{- |
  Constructs a 'CreateTableExpr' with the given options.

  @since 1.0.0.0
-}
createTableExpr ::
  -- | The name to be used for the table.
  Qualified TableName ->
  -- | The columns to include in the table.
  [ColumnDefinition] ->
  -- | A primary key expression for the table.
  Maybe PrimaryKeyExpr ->
  -- | Any table constraints to include with the table.
  [TableConstraint] ->
  CreateTableExpr
createTableExpr tableName columnDefs mbPrimaryKey constraints =
  let
    columnDefsSql =
      map RawSql.toRawSql columnDefs

    constraintsSql =
      map RawSql.toRawSql constraints

    tableElementsSql =
      case mbPrimaryKey of
        Nothing ->
          columnDefsSql <> constraintsSql
        Just primaryKey ->
          RawSql.toRawSql primaryKey : (columnDefsSql <> constraintsSql)
  in
    CreateTableExpr $
      mconcat
        [ RawSql.fromString "CREATE TABLE "
        , RawSql.toRawSql tableName
        , RawSql.space
        , RawSql.leftParen
        , RawSql.intercalate RawSql.comma tableElementsSql
        , RawSql.rightParen
        ]

{- |
Type to represent the primary key of a table. E.G.

> PRIMARY KEY (id)

'PrimaryKeyExpr' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.

@since 1.0.0.0
-}
newtype PrimaryKeyExpr
  = PrimaryKeyExpr RawSql.RawSql
  deriving (RawSql.SqlExpression)

{- |
  Constructs a 'PrimaryKeyExpr' with the given columns.

  @since 1.0.0.0
-}
primaryKeyExpr :: NonEmpty ColumnName -> PrimaryKeyExpr
primaryKeyExpr columnNames =
  PrimaryKeyExpr $
    mconcat
      [ RawSql.fromString "PRIMARY KEY "
      , RawSql.leftParen
      , RawSql.intercalate RawSql.comma columnNames
      , RawSql.rightParen
      ]

{- |
Type to represent an @ALTER TABLE@ statement. E.G.

> ALTER TABLE foo ADD COLUMN bar integer

'AlterTableExpr' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.

@since 1.0.0.0
-}
newtype AlterTableExpr
  = AlterTableExpr RawSql.RawSql
  deriving (RawSql.SqlExpression)

{- |
  Constructs an 'AlterTableExpr' with the given alter table actions.

  @since 1.0.0.0
-}
alterTableExpr :: Qualified TableName -> NonEmpty AlterTableAction -> AlterTableExpr
alterTableExpr tableName actions =
  AlterTableExpr $
    RawSql.fromString "ALTER TABLE "
      <> RawSql.toRawSql tableName
      <> RawSql.space
      <> RawSql.intercalate RawSql.commaSpace actions

{- |
Type to represent an action as part of an @ALTER TABLE@ statement. E.G.

> ADD COLUMN bar integer

'AlterTableAction' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.

@since 1.0.0.0
-}
newtype AlterTableAction
  = AlterTableAction RawSql.RawSql
  deriving (RawSql.SqlExpression)

{- |
  Constructs an 'AlterTableAction' that will add the specified column to the
  table.

  @since 1.0.0.0
-}
addColumn :: ColumnDefinition -> AlterTableAction
addColumn columnDef =
  AlterTableAction $
    RawSql.fromString "ADD COLUMN " <> RawSql.toRawSql columnDef

{- |
  Constructs an 'AlterTableAction' that will drop the specified column from the
  table.

  @since 1.0.0.0
-}
dropColumn :: ColumnName -> AlterTableAction
dropColumn columnName =
  AlterTableAction $
    RawSql.fromString "DROP COLUMN " <> RawSql.toRawSql columnName

{- |
  Constructs an 'AlterTableAction' that will add the specified constraint to the
  table.

  @since 1.0.0.0
-}
addConstraint :: TableConstraint -> AlterTableAction
addConstraint constraint =
  AlterTableAction $
    RawSql.fromString "ADD " <> RawSql.toRawSql constraint

{- |
  Constructs an 'AlterTableAction' that will drop the specified constraint from the
  table.

  @since 1.0.0.0
-}
dropConstraint :: ConstraintName -> AlterTableAction
dropConstraint constraintName =
  AlterTableAction $
    RawSql.fromString "DROP CONSTRAINT " <> RawSql.toRawSql constraintName

{- |
  Constructs an 'AlterTableAction' that will alter the type of the specified
  column.

  @since 1.0.0.0
-}
alterColumnType ::
  -- | The name of the column whose type will be altered.
  ColumnName ->
  -- | The new type to use for the column.
  DataType ->
  -- | An optional 'UsingClause' to indicate to the database how data from the
  -- old type should be converted to the new type.
  Maybe UsingClause ->
  AlterTableAction
alterColumnType columnName dataType maybeUsingClause =
  AlterTableAction $
    RawSql.intercalate
      RawSql.space
      ( RawSql.fromString "ALTER COLUMN"
          : RawSql.toRawSql columnName
          : RawSql.fromString "TYPE"
          : RawSql.toRawSql dataType
          : maybeToList (fmap RawSql.toRawSql maybeUsingClause)
      )

{- |
Type to represent a @USING@ clause as part of an @ALTER COLUMN@ when changing
the type of a column. E.G.

> USING id :: integer

'UsingClause' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.

@since 1.0.0.0
-}
newtype UsingClause
  = UsingClause RawSql.RawSql
  deriving (RawSql.SqlExpression)

{- |
  Constructs a 'UsingClause' that will cast the column to the specified type.

  @since 1.0.0.0
-}
usingCast :: ColumnName -> DataType -> UsingClause
usingCast columnName dataType =
  UsingClause $
    RawSql.fromString "USING "
      <> RawSql.toRawSql columnName
      <> RawSql.doubleColon
      <> RawSql.toRawSql dataType

{- |
  Constructs an 'AlterTableAction' that will alter the nullability of the
  column.

  @since 1.0.0.0
-}
alterColumnNullability :: ColumnName -> AlterNotNull -> AlterTableAction
alterColumnNullability columnName alterNotNull =
  AlterTableAction $
    RawSql.intercalate
      RawSql.space
      [ RawSql.fromString "ALTER COLUMN"
      , RawSql.toRawSql columnName
      , RawSql.toRawSql alterNotNull
      ]

{- |
Type to represent an action to alter the nullability of a column. E.G.

> SET NOT NULL

'AlterNotNull' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.

@since 1.0.0.0
-}
newtype AlterNotNull
  = AlterNotNull RawSql.RawSql
  deriving (RawSql.SqlExpression)

{- |
  Sets the column to not null via @SET NOT NULL@.

  @since 1.0.0.0
-}
setNotNull :: AlterNotNull
setNotNull =
  AlterNotNull $ RawSql.fromString "SET NOT NULL"

{- |
  Sets the column to allow null via @DROP NOT NULL@.

  @since 1.0.0.0
-}
dropNotNull :: AlterNotNull
dropNotNull =
  AlterNotNull $ RawSql.fromString "DROP NOT NULL"

{- |
  Constructs an 'AlterTableAction' that will use @DROP DEFAULT@ to drop the
  default value of the specified column.

  @since 1.0.0.0
-}
alterColumnDropDefault :: ColumnName -> AlterTableAction
alterColumnDropDefault columnName =
  AlterTableAction $
    RawSql.intercalate
      RawSql.space
      [ RawSql.fromString "ALTER COLUMN"
      , RawSql.toRawSql columnName
      , RawSql.fromString "DROP DEFAULT"
      ]

{- |
  Constructs an 'AlterTableAction' that will use @SET DEFAULT@ to set the
  default value of the specified column.

  @since 1.0.0.0
-}
alterColumnSetDefault ::
  RawSql.SqlExpression valueExpression =>
  ColumnName ->
  valueExpression ->
  AlterTableAction
alterColumnSetDefault columnName defaultValue =
  AlterTableAction $
    RawSql.intercalate
      RawSql.space
      [ RawSql.fromString "ALTER COLUMN"
      , RawSql.toRawSql columnName
      , RawSql.fromString "SET DEFAULT"
      , RawSql.toRawSql defaultValue
      ]

{- |
Type to represent a @DROP TABLE@ statement. E.G.

> DROP TABLE FOO

'DropTableExpr' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.

@since 1.0.0.0
-}
newtype DropTableExpr
  = DropTableExpr RawSql.RawSql
  deriving (RawSql.SqlExpression)

{- |
  Constructs a 'DropTableExpr' that will drop the specified table.

  @since 1.0.0.0
-}
dropTableExpr :: Maybe IfExists -> Qualified TableName -> DropTableExpr
dropTableExpr maybeIfExists tableName =
  DropTableExpr $
    RawSql.intercalate
      RawSql.space
      ( catMaybes
          [ Just (RawSql.fromString "DROP TABLE")
          , fmap RawSql.toRawSql maybeIfExists
          , Just (RawSql.toRawSql tableName)
          ]
      )