{-# LANGUAGE GeneralizedNewtypeDeriving #-}

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

@since 1.0.0.0
-}
module Orville.PostgreSQL.Expr.Index
  ( CreateIndexExpr
  , createIndexExpr
  , IndexUniqueness (UniqueIndex, NonUniqueIndex)
  , IndexBodyExpr
  , indexBodyColumns
  , ConcurrentlyExpr
  , concurrently
  , DropIndexExpr
  , dropIndexExpr
  , createNamedIndexExpr
  )
where

import Data.List.NonEmpty (NonEmpty)

import Orville.PostgreSQL.Expr.Name (ColumnName, IndexName, Qualified, TableName)
import qualified Orville.PostgreSQL.Raw.RawSql as RawSql

{- |
Type to represent a SQL "CREATE INDEX" statement. E.G.

> CREATE INDEX ON table (foo, bar, baz)

'CreateIndexExpr' 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 CreateIndexExpr
  = CreateIndexExpr RawSql.RawSql
  deriving
    ( -- | @since 1.0.0.0
      RawSql -> CreateIndexExpr
CreateIndexExpr -> RawSql
(CreateIndexExpr -> RawSql)
-> (RawSql -> CreateIndexExpr) -> SqlExpression CreateIndexExpr
forall a. (a -> RawSql) -> (RawSql -> a) -> SqlExpression a
$ctoRawSql :: CreateIndexExpr -> RawSql
toRawSql :: CreateIndexExpr -> RawSql
$cunsafeFromRawSql :: RawSql -> CreateIndexExpr
unsafeFromRawSql :: RawSql -> CreateIndexExpr
RawSql.SqlExpression
    )

{- |
Construct a SQL CREATE INDEX from an indicator of if the index should be
unique, a table, and corresponding collection of 'ColumnName's.

@since 1.0.0.0
-}
createIndexExpr ::
  IndexUniqueness ->
  Maybe ConcurrentlyExpr ->
  Qualified TableName ->
  NonEmpty ColumnName ->
  CreateIndexExpr
createIndexExpr :: IndexUniqueness
-> Maybe ConcurrentlyExpr
-> Qualified TableName
-> NonEmpty ColumnName
-> CreateIndexExpr
createIndexExpr IndexUniqueness
uniqueness Maybe ConcurrentlyExpr
mbConcurrently Qualified TableName
tableName NonEmpty ColumnName
columns =
  RawSql -> CreateIndexExpr
CreateIndexExpr (RawSql -> CreateIndexExpr) -> RawSql -> CreateIndexExpr
forall a b. (a -> b) -> a -> b
$
    String -> RawSql
RawSql.fromString String
"CREATE "
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> IndexUniqueness -> RawSql
uniquenessToSql IndexUniqueness
uniqueness
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> String -> RawSql
RawSql.fromString String
"INDEX "
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql -> (RawSql -> RawSql) -> Maybe RawSql -> RawSql
forall b a. b -> (a -> b) -> Maybe a -> b
maybe RawSql
forall a. Monoid a => a
mempty (RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.space) ((ConcurrentlyExpr -> RawSql)
-> Maybe ConcurrentlyExpr -> Maybe RawSql
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ConcurrentlyExpr -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql Maybe ConcurrentlyExpr
mbConcurrently)
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> String -> RawSql
RawSql.fromString String
"ON "
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> Qualified TableName -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql Qualified TableName
tableName
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.space
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> IndexBodyExpr -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql (NonEmpty ColumnName -> IndexBodyExpr
indexBodyColumns NonEmpty ColumnName
columns)

{- |
Construct a SQL CREATE INDEX from an indicator of if the index should be
unique, a table, a name for the index, and some SQL representing the rest of
the index creation.

@since 1.0.0.0
-}
createNamedIndexExpr ::
  IndexUniqueness ->
  Maybe ConcurrentlyExpr ->
  Qualified TableName ->
  IndexName ->
  IndexBodyExpr ->
  CreateIndexExpr
createNamedIndexExpr :: IndexUniqueness
-> Maybe ConcurrentlyExpr
-> Qualified TableName
-> IndexName
-> IndexBodyExpr
-> CreateIndexExpr
createNamedIndexExpr IndexUniqueness
uniqueness Maybe ConcurrentlyExpr
mbConcurrently Qualified TableName
tableName IndexName
indexName IndexBodyExpr
bodyExpr =
  RawSql -> CreateIndexExpr
CreateIndexExpr (RawSql -> CreateIndexExpr) -> RawSql -> CreateIndexExpr
forall a b. (a -> b) -> a -> b
$
    String -> RawSql
RawSql.fromString String
"CREATE "
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> IndexUniqueness -> RawSql
uniquenessToSql IndexUniqueness
uniqueness
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> String -> RawSql
RawSql.fromString String
"INDEX "
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql -> (RawSql -> RawSql) -> Maybe RawSql -> RawSql
forall b a. b -> (a -> b) -> Maybe a -> b
maybe RawSql
forall a. Monoid a => a
mempty (RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.space) ((ConcurrentlyExpr -> RawSql)
-> Maybe ConcurrentlyExpr -> Maybe RawSql
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ConcurrentlyExpr -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql Maybe ConcurrentlyExpr
mbConcurrently)
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> IndexName -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql IndexName
indexName
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> String -> RawSql
RawSql.fromString String
" ON "
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> Qualified TableName -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql Qualified TableName
tableName
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.space
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> IndexBodyExpr -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql IndexBodyExpr
bodyExpr

{- |
Type to represent the @CONCURRENTLY@ keyword for index creation. E.G.

> CONCURRENTLY

'ConcurrentlyExpr' 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 ConcurrentlyExpr
  = ConcurrentlyExpr RawSql.RawSql
  deriving
    ( -- | @since 1.0.0.0
      RawSql -> ConcurrentlyExpr
ConcurrentlyExpr -> RawSql
(ConcurrentlyExpr -> RawSql)
-> (RawSql -> ConcurrentlyExpr) -> SqlExpression ConcurrentlyExpr
forall a. (a -> RawSql) -> (RawSql -> a) -> SqlExpression a
$ctoRawSql :: ConcurrentlyExpr -> RawSql
toRawSql :: ConcurrentlyExpr -> RawSql
$cunsafeFromRawSql :: RawSql -> ConcurrentlyExpr
unsafeFromRawSql :: RawSql -> ConcurrentlyExpr
RawSql.SqlExpression
    )

{- |
The @CONCURRENTLY@ keyword indicates to PostgreSQL that an index should be
created concurrently.

@since 1.0.0.0
-}
concurrently :: ConcurrentlyExpr
concurrently :: ConcurrentlyExpr
concurrently =
  String -> ConcurrentlyExpr
forall a. SqlExpression a => String -> a
RawSql.unsafeSqlExpression String
"CONCURRENTLY"

{- |
Type to represent the body of an index definition E.G.

> (foo, bar)

in

> CREATE some_index ON some_table (foo, bar)

'IndexBodyExpr' 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 IndexBodyExpr
  = IndexBodyExpr RawSql.RawSql
  deriving
    ( -- | @since 1.0.0.0
      RawSql -> IndexBodyExpr
IndexBodyExpr -> RawSql
(IndexBodyExpr -> RawSql)
-> (RawSql -> IndexBodyExpr) -> SqlExpression IndexBodyExpr
forall a. (a -> RawSql) -> (RawSql -> a) -> SqlExpression a
$ctoRawSql :: IndexBodyExpr -> RawSql
toRawSql :: IndexBodyExpr -> RawSql
$cunsafeFromRawSql :: RawSql -> IndexBodyExpr
unsafeFromRawSql :: RawSql -> IndexBodyExpr
RawSql.SqlExpression
    )

{- |
Creates an 'IndexBodyExpr' for the given column names. The resulting
SQL looks like @(column1, column2, ...)@.

@since 1.0.0.0
-}
indexBodyColumns ::
  NonEmpty ColumnName ->
  IndexBodyExpr
indexBodyColumns :: NonEmpty ColumnName -> IndexBodyExpr
indexBodyColumns NonEmpty ColumnName
columns =
  RawSql -> IndexBodyExpr
IndexBodyExpr (RawSql -> IndexBodyExpr) -> RawSql -> IndexBodyExpr
forall a b. (a -> b) -> a -> b
$
    RawSql
RawSql.leftParen
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql -> NonEmpty ColumnName -> RawSql
forall sql (f :: * -> *).
(SqlExpression sql, Foldable f) =>
RawSql -> f sql -> RawSql
RawSql.intercalate RawSql
RawSql.comma NonEmpty ColumnName
columns
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.rightParen

{- |
Type to represent if an index should be unique.

@since 1.0.0.0
-}
data IndexUniqueness
  = UniqueIndex
  | NonUniqueIndex
  deriving
    ( -- | @since 1.0.0.0
      IndexUniqueness -> IndexUniqueness -> Bool
(IndexUniqueness -> IndexUniqueness -> Bool)
-> (IndexUniqueness -> IndexUniqueness -> Bool)
-> Eq IndexUniqueness
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: IndexUniqueness -> IndexUniqueness -> Bool
== :: IndexUniqueness -> IndexUniqueness -> Bool
$c/= :: IndexUniqueness -> IndexUniqueness -> Bool
/= :: IndexUniqueness -> IndexUniqueness -> Bool
Eq
    , -- | @since 1.0.0.0
      Eq IndexUniqueness
Eq IndexUniqueness
-> (IndexUniqueness -> IndexUniqueness -> Ordering)
-> (IndexUniqueness -> IndexUniqueness -> Bool)
-> (IndexUniqueness -> IndexUniqueness -> Bool)
-> (IndexUniqueness -> IndexUniqueness -> Bool)
-> (IndexUniqueness -> IndexUniqueness -> Bool)
-> (IndexUniqueness -> IndexUniqueness -> IndexUniqueness)
-> (IndexUniqueness -> IndexUniqueness -> IndexUniqueness)
-> Ord IndexUniqueness
IndexUniqueness -> IndexUniqueness -> Bool
IndexUniqueness -> IndexUniqueness -> Ordering
IndexUniqueness -> IndexUniqueness -> IndexUniqueness
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: IndexUniqueness -> IndexUniqueness -> Ordering
compare :: IndexUniqueness -> IndexUniqueness -> Ordering
$c< :: IndexUniqueness -> IndexUniqueness -> Bool
< :: IndexUniqueness -> IndexUniqueness -> Bool
$c<= :: IndexUniqueness -> IndexUniqueness -> Bool
<= :: IndexUniqueness -> IndexUniqueness -> Bool
$c> :: IndexUniqueness -> IndexUniqueness -> Bool
> :: IndexUniqueness -> IndexUniqueness -> Bool
$c>= :: IndexUniqueness -> IndexUniqueness -> Bool
>= :: IndexUniqueness -> IndexUniqueness -> Bool
$cmax :: IndexUniqueness -> IndexUniqueness -> IndexUniqueness
max :: IndexUniqueness -> IndexUniqueness -> IndexUniqueness
$cmin :: IndexUniqueness -> IndexUniqueness -> IndexUniqueness
min :: IndexUniqueness -> IndexUniqueness -> IndexUniqueness
Ord
    , -- | @since 1.0.0.0
      Int -> IndexUniqueness -> ShowS
[IndexUniqueness] -> ShowS
IndexUniqueness -> String
(Int -> IndexUniqueness -> ShowS)
-> (IndexUniqueness -> String)
-> ([IndexUniqueness] -> ShowS)
-> Show IndexUniqueness
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> IndexUniqueness -> ShowS
showsPrec :: Int -> IndexUniqueness -> ShowS
$cshow :: IndexUniqueness -> String
show :: IndexUniqueness -> String
$cshowList :: [IndexUniqueness] -> ShowS
showList :: [IndexUniqueness] -> ShowS
Show
    )

-- Internal helper
uniquenessToSql :: IndexUniqueness -> RawSql.RawSql
uniquenessToSql :: IndexUniqueness -> RawSql
uniquenessToSql IndexUniqueness
uniqueness =
  case IndexUniqueness
uniqueness of
    IndexUniqueness
UniqueIndex -> String -> RawSql
RawSql.fromString String
"UNIQUE "
    IndexUniqueness
NonUniqueIndex -> RawSql
forall a. Monoid a => a
mempty

{- |
Type to represent a SQL "DROP INDEX" statement. E.G.

> DROP INDEX foo

'DropIndexExpr' 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 DropIndexExpr
  = DropIndexExpr RawSql.RawSql
  deriving
    ( -- | @since 1.0.0.0
      RawSql -> DropIndexExpr
DropIndexExpr -> RawSql
(DropIndexExpr -> RawSql)
-> (RawSql -> DropIndexExpr) -> SqlExpression DropIndexExpr
forall a. (a -> RawSql) -> (RawSql -> a) -> SqlExpression a
$ctoRawSql :: DropIndexExpr -> RawSql
toRawSql :: DropIndexExpr -> RawSql
$cunsafeFromRawSql :: RawSql -> DropIndexExpr
unsafeFromRawSql :: RawSql -> DropIndexExpr
RawSql.SqlExpression
    )

{- |
Construct a SQL DROP INDEX for a given 'IndexName'.

@since 1.0.0.0
-}
dropIndexExpr :: IndexName -> DropIndexExpr
dropIndexExpr :: IndexName -> DropIndexExpr
dropIndexExpr IndexName
indexName =
  RawSql -> DropIndexExpr
DropIndexExpr (RawSql -> DropIndexExpr) -> RawSql -> DropIndexExpr
forall a b. (a -> b) -> a -> b
$
    String -> RawSql
RawSql.fromString String
"DROP INDEX " RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> IndexName -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql IndexName
indexName