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

@since 1.0.0.0
-}
module Orville.PostgreSQL.Execution.SelectOptions
  ( SelectOptions
  , emptySelectOptions
  , appendSelectOptions
  , selectDistinct
  , selectWhereClause
  , selectOrderByClause
  , selectGroupByClause
  , selectLimitExpr
  , selectOffsetExpr
  , distinct
  , where_
  , orderBy
  , limit
  , offset
  , groupBy
  , selectOptionsQueryExpr
  )
where

import Data.Monoid (First (First, getFirst))

import qualified Orville.PostgreSQL.Expr as Expr

{- |
   A 'SelectOptions' is a set of options that can be used to change the way
   a basic query function works by adding @WHERE@, @ORDER BY@, @GROUP BY@, etc.
   Functions are provided to construct 'SelectOptions' for individual options,
   which may then be combined via '<>' (also exposed as 'appendSelectOptions').

@since 1.0.0.0
-}
data SelectOptions = SelectOptions
  { SelectOptions -> First Bool
i_distinct :: First Bool
  , SelectOptions -> Maybe BooleanExpr
i_whereCondition :: Maybe Expr.BooleanExpr
  , SelectOptions -> Maybe OrderByExpr
i_orderBy :: Maybe Expr.OrderByExpr
  , SelectOptions -> First LimitExpr
i_limitExpr :: First Expr.LimitExpr
  , SelectOptions -> First OffsetExpr
i_offsetExpr :: First Expr.OffsetExpr
  , SelectOptions -> Maybe GroupByExpr
i_groupByExpr :: Maybe Expr.GroupByExpr
  }

-- | @since 1.0.0.0
instance Semigroup SelectOptions where
  <> :: SelectOptions -> SelectOptions -> SelectOptions
(<>) = SelectOptions -> SelectOptions -> SelectOptions
appendSelectOptions

-- | @since 1.0.0.0
instance Monoid SelectOptions where
  mempty :: SelectOptions
mempty = SelectOptions
emptySelectOptions

{- |
  A set of empty 'SelectOptions' that will not change how a query is run.

@since 1.0.0.0
-}
emptySelectOptions :: SelectOptions
emptySelectOptions :: SelectOptions
emptySelectOptions =
  SelectOptions
    { i_distinct :: First Bool
i_distinct = First Bool
forall a. Monoid a => a
mempty
    , i_whereCondition :: Maybe BooleanExpr
i_whereCondition = Maybe BooleanExpr
forall a. Maybe a
Nothing
    , i_orderBy :: Maybe OrderByExpr
i_orderBy = Maybe OrderByExpr
forall a. Monoid a => a
mempty
    , i_limitExpr :: First LimitExpr
i_limitExpr = First LimitExpr
forall a. Monoid a => a
mempty
    , i_offsetExpr :: First OffsetExpr
i_offsetExpr = First OffsetExpr
forall a. Monoid a => a
mempty
    , i_groupByExpr :: Maybe GroupByExpr
i_groupByExpr = Maybe GroupByExpr
forall a. Monoid a => a
mempty
    }

{- |
  Combines multple select options together, unioning the options together where
  possible. For options where this is not possible (e.g. @LIMIT@), the one
  on the left is preferred.

@since 1.0.0.0
-}
appendSelectOptions :: SelectOptions -> SelectOptions -> SelectOptions
appendSelectOptions :: SelectOptions -> SelectOptions -> SelectOptions
appendSelectOptions SelectOptions
left SelectOptions
right =
  First Bool
-> Maybe BooleanExpr
-> Maybe OrderByExpr
-> First LimitExpr
-> First OffsetExpr
-> Maybe GroupByExpr
-> SelectOptions
SelectOptions
    (SelectOptions -> First Bool
i_distinct SelectOptions
left First Bool -> First Bool -> First Bool
forall a. Semigroup a => a -> a -> a
<> SelectOptions -> First Bool
i_distinct SelectOptions
right)
    ((BooleanExpr -> BooleanExpr -> BooleanExpr)
-> Maybe BooleanExpr -> Maybe BooleanExpr -> Maybe BooleanExpr
forall a. (a -> a -> a) -> Maybe a -> Maybe a -> Maybe a
unionMaybeWith BooleanExpr -> BooleanExpr -> BooleanExpr
Expr.andExpr (SelectOptions -> Maybe BooleanExpr
i_whereCondition SelectOptions
left) (SelectOptions -> Maybe BooleanExpr
i_whereCondition SelectOptions
right))
    (SelectOptions -> Maybe OrderByExpr
i_orderBy SelectOptions
left Maybe OrderByExpr -> Maybe OrderByExpr -> Maybe OrderByExpr
forall a. Semigroup a => a -> a -> a
<> SelectOptions -> Maybe OrderByExpr
i_orderBy SelectOptions
right)
    (SelectOptions -> First LimitExpr
i_limitExpr SelectOptions
left First LimitExpr -> First LimitExpr -> First LimitExpr
forall a. Semigroup a => a -> a -> a
<> SelectOptions -> First LimitExpr
i_limitExpr SelectOptions
right)
    (SelectOptions -> First OffsetExpr
i_offsetExpr SelectOptions
left First OffsetExpr -> First OffsetExpr -> First OffsetExpr
forall a. Semigroup a => a -> a -> a
<> SelectOptions -> First OffsetExpr
i_offsetExpr SelectOptions
right)
    (SelectOptions -> Maybe GroupByExpr
i_groupByExpr SelectOptions
left Maybe GroupByExpr -> Maybe GroupByExpr -> Maybe GroupByExpr
forall a. Semigroup a => a -> a -> a
<> SelectOptions -> Maybe GroupByExpr
i_groupByExpr SelectOptions
right)

unionMaybeWith :: (a -> a -> a) -> Maybe a -> Maybe a -> Maybe a
unionMaybeWith :: forall a. (a -> a -> a) -> Maybe a -> Maybe a -> Maybe a
unionMaybeWith a -> a -> a
f Maybe a
mbLeft Maybe a
mbRight =
  case (Maybe a
mbLeft, Maybe a
mbRight) of
    (Maybe a
Nothing, Maybe a
Nothing) -> Maybe a
forall a. Maybe a
Nothing
    (Just a
left, Maybe a
Nothing) -> a -> Maybe a
forall a. a -> Maybe a
Just a
left
    (Maybe a
Nothing, Just a
right) -> a -> Maybe a
forall a. a -> Maybe a
Just a
right
    (Just a
left, Just a
right) -> a -> Maybe a
forall a. a -> Maybe a
Just (a -> a -> a
f a
left a
right)

{- |
  Builds the 'Expr.SelectClause' that should be used to include the
  'distinct's from the 'SelectOptions' on a query.

@since 1.0.0.0
-}
selectDistinct :: SelectOptions -> Expr.SelectClause
selectDistinct :: SelectOptions -> SelectClause
selectDistinct SelectOptions
selectOptions =
  case SelectOptions -> First Bool
i_distinct SelectOptions
selectOptions of
    First (Just Bool
True) -> SelectExpr -> SelectClause
Expr.selectClause (SelectExpr -> SelectClause)
-> (Maybe Distinct -> SelectExpr) -> Maybe Distinct -> SelectClause
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe Distinct -> SelectExpr
Expr.selectExpr (Maybe Distinct -> SelectClause) -> Maybe Distinct -> SelectClause
forall a b. (a -> b) -> a -> b
$ Distinct -> Maybe Distinct
forall a. a -> Maybe a
Just Distinct
Expr.Distinct
    First Bool
_ -> SelectExpr -> SelectClause
Expr.selectClause (SelectExpr -> SelectClause) -> SelectExpr -> SelectClause
forall a b. (a -> b) -> a -> b
$ Maybe Distinct -> SelectExpr
Expr.selectExpr Maybe Distinct
forall a. Maybe a
Nothing

{- |
  Builds the 'Expr.WhereClause' that should be used to include the
  'Expr.BooleanExpr's from the 'SelectOptions' on a query. This will be 'Nothing'
  when no 'Expr.BooleanExpr's have been specified.

@since 1.0.0.0
-}
selectWhereClause :: SelectOptions -> Maybe Expr.WhereClause
selectWhereClause :: SelectOptions -> Maybe WhereClause
selectWhereClause =
  (BooleanExpr -> WhereClause)
-> Maybe BooleanExpr -> Maybe WhereClause
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap BooleanExpr -> WhereClause
Expr.whereClause (Maybe BooleanExpr -> Maybe WhereClause)
-> (SelectOptions -> Maybe BooleanExpr)
-> SelectOptions
-> Maybe WhereClause
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SelectOptions -> Maybe BooleanExpr
i_whereCondition

{- |
  Constructs a 'SelectOptions' with just 'distinct' set to 'True'.

@since 1.0.0.0
-}
distinct :: SelectOptions
distinct :: SelectOptions
distinct =
  SelectOptions
emptySelectOptions
    { i_distinct :: First Bool
i_distinct = Maybe Bool -> First Bool
forall a. Maybe a -> First a
First (Maybe Bool -> First Bool) -> Maybe Bool -> First Bool
forall a b. (a -> b) -> a -> b
$ Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
    }

{- |
  Builds the 'Expr.OrderByClause' that should be used to include the
  'Expr.OrderByClause's from the 'SelectOptions' on a query. This will be
  'Nothing' when no 'Expr.OrderByClause's have been specified.

@since 1.0.0.0
-}
selectOrderByClause :: SelectOptions -> Maybe Expr.OrderByClause
selectOrderByClause :: SelectOptions -> Maybe OrderByClause
selectOrderByClause =
  (OrderByExpr -> OrderByClause)
-> Maybe OrderByExpr -> Maybe OrderByClause
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap OrderByExpr -> OrderByClause
Expr.orderByClause (Maybe OrderByExpr -> Maybe OrderByClause)
-> (SelectOptions -> Maybe OrderByExpr)
-> SelectOptions
-> Maybe OrderByClause
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SelectOptions -> Maybe OrderByExpr
i_orderBy

{- |
  Builds the 'Expr.GroupByClause' that should be used to include the
  'Expr.GroupByClause's from the 'SelectOptions' on a query. This will be
  'Nothing' when no 'Expr.GroupByClause's have been specified.

@since 1.0.0.0
-}
selectGroupByClause :: SelectOptions -> Maybe Expr.GroupByClause
selectGroupByClause :: SelectOptions -> Maybe GroupByClause
selectGroupByClause =
  (GroupByExpr -> GroupByClause)
-> Maybe GroupByExpr -> Maybe GroupByClause
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap GroupByExpr -> GroupByClause
Expr.groupByClause (Maybe GroupByExpr -> Maybe GroupByClause)
-> (SelectOptions -> Maybe GroupByExpr)
-> SelectOptions
-> Maybe GroupByClause
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SelectOptions -> Maybe GroupByExpr
i_groupByExpr

{- |
  Builds a 'Expr.LimitExpr' that will limit the query results to the
  number specified in the 'SelectOptions' (if any).

@since 1.0.0.0
-}
selectLimitExpr :: SelectOptions -> Maybe Expr.LimitExpr
selectLimitExpr :: SelectOptions -> Maybe LimitExpr
selectLimitExpr =
  First LimitExpr -> Maybe LimitExpr
forall a. First a -> Maybe a
getFirst (First LimitExpr -> Maybe LimitExpr)
-> (SelectOptions -> First LimitExpr)
-> SelectOptions
-> Maybe LimitExpr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SelectOptions -> First LimitExpr
i_limitExpr

{- |
  Builds an 'Expr.OffsetExpr' that will limit the query results to the
  number specified in the 'SelectOptions' (if any).

@since 1.0.0.0
-}
selectOffsetExpr :: SelectOptions -> Maybe Expr.OffsetExpr
selectOffsetExpr :: SelectOptions -> Maybe OffsetExpr
selectOffsetExpr =
  First OffsetExpr -> Maybe OffsetExpr
forall a. First a -> Maybe a
getFirst (First OffsetExpr -> Maybe OffsetExpr)
-> (SelectOptions -> First OffsetExpr)
-> SelectOptions
-> Maybe OffsetExpr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SelectOptions -> First OffsetExpr
i_offsetExpr

{- |
  Constructs a 'SelectOptions' with just the given 'Expr.BooleanExpr'.

@since 1.0.0.0
-}
where_ :: Expr.BooleanExpr -> SelectOptions
where_ :: BooleanExpr -> SelectOptions
where_ BooleanExpr
condition =
  SelectOptions
emptySelectOptions
    { i_whereCondition :: Maybe BooleanExpr
i_whereCondition = BooleanExpr -> Maybe BooleanExpr
forall a. a -> Maybe a
Just BooleanExpr
condition
    }

{- |
  Constructs a 'SelectOptions' with just the given 'Expr.OrderByExpr'.

@since 1.0.0.0
-}
orderBy :: Expr.OrderByExpr -> SelectOptions
orderBy :: OrderByExpr -> SelectOptions
orderBy OrderByExpr
order =
  SelectOptions
emptySelectOptions
    { i_orderBy :: Maybe OrderByExpr
i_orderBy = OrderByExpr -> Maybe OrderByExpr
forall a. a -> Maybe a
Just OrderByExpr
order
    }

{- |
  Constructs a 'SelectOptions' that will apply the given limit.

@since 1.0.0.0
-}
limit :: Int -> SelectOptions
limit :: Int -> SelectOptions
limit Int
limitValue =
  SelectOptions
emptySelectOptions
    { i_limitExpr :: First LimitExpr
i_limitExpr = Maybe LimitExpr -> First LimitExpr
forall a. Maybe a -> First a
First (Maybe LimitExpr -> First LimitExpr)
-> (Int -> Maybe LimitExpr) -> Int -> First LimitExpr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. LimitExpr -> Maybe LimitExpr
forall a. a -> Maybe a
Just (LimitExpr -> Maybe LimitExpr)
-> (Int -> LimitExpr) -> Int -> Maybe LimitExpr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> LimitExpr
Expr.limitExpr (Int -> First LimitExpr) -> Int -> First LimitExpr
forall a b. (a -> b) -> a -> b
$ Int
limitValue
    }

{- |
  Constructs a 'SelectOptions' that will apply the given offset.

@since 1.0.0.0
-}
offset :: Int -> SelectOptions
offset :: Int -> SelectOptions
offset Int
offsetValue =
  SelectOptions
emptySelectOptions
    { i_offsetExpr :: First OffsetExpr
i_offsetExpr = Maybe OffsetExpr -> First OffsetExpr
forall a. Maybe a -> First a
First (Maybe OffsetExpr -> First OffsetExpr)
-> (Int -> Maybe OffsetExpr) -> Int -> First OffsetExpr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. OffsetExpr -> Maybe OffsetExpr
forall a. a -> Maybe a
Just (OffsetExpr -> Maybe OffsetExpr)
-> (Int -> OffsetExpr) -> Int -> Maybe OffsetExpr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> OffsetExpr
Expr.offsetExpr (Int -> First OffsetExpr) -> Int -> First OffsetExpr
forall a b. (a -> b) -> a -> b
$ Int
offsetValue
    }

{- |
  Constructs a 'SelectOptions' with just the given 'Expr.GroupByClause'.

@since 1.0.0.0
-}
groupBy :: Expr.GroupByExpr -> SelectOptions
groupBy :: GroupByExpr -> SelectOptions
groupBy GroupByExpr
groupByExpr =
  SelectOptions
emptySelectOptions
    { i_groupByExpr :: Maybe GroupByExpr
i_groupByExpr = GroupByExpr -> Maybe GroupByExpr
forall a. a -> Maybe a
Just GroupByExpr
groupByExpr
    }

{- |
  Builds a 'Expr.QueryExpr' that will use the specified 'Expr.SelectList' when
  building the @SELECT@ statement to execute. It is up to the caller to make
  sure that the 'Expr.SelectList' expression makes sense for the table being
  queried, and that the names of the columns in the result set match those
  expected by the 'Orville.PostgreSQL.SqlMarshaller' that is ultimately used to
  decode it.

  This function is useful for building more advanced queries that need to
  select things other than simple columns from the table, such as using
  aggregate functions. The 'Expr.SelectList' can be built however the caller
  desires. If Orville does not support building the 'Expr.SelectList' you need
  using any of the expression-building functions, you can resort to
  @RawSql.fromRawSql@ as an escape hatch to build the 'Expr.SelectList' here.

@since 1.0.0.0
-}
selectOptionsQueryExpr ::
  Expr.SelectList ->
  Expr.TableReferenceList ->
  SelectOptions ->
  Expr.QueryExpr
selectOptionsQueryExpr :: SelectList -> TableReferenceList -> SelectOptions -> QueryExpr
selectOptionsQueryExpr SelectList
selectList TableReferenceList
tableReferenceList SelectOptions
selectOptions =
  SelectClause -> SelectList -> Maybe TableExpr -> QueryExpr
Expr.queryExpr
    (SelectOptions -> SelectClause
selectDistinct SelectOptions
selectOptions)
    SelectList
selectList
    ( TableExpr -> Maybe TableExpr
forall a. a -> Maybe a
Just (TableExpr -> Maybe TableExpr) -> TableExpr -> Maybe TableExpr
forall a b. (a -> b) -> a -> b
$
        TableReferenceList
-> Maybe WhereClause
-> Maybe GroupByClause
-> Maybe OrderByClause
-> Maybe LimitExpr
-> Maybe OffsetExpr
-> TableExpr
Expr.tableExpr
          TableReferenceList
tableReferenceList
          (SelectOptions -> Maybe WhereClause
selectWhereClause SelectOptions
selectOptions)
          (SelectOptions -> Maybe GroupByClause
selectGroupByClause SelectOptions
selectOptions)
          (SelectOptions -> Maybe OrderByClause
selectOrderByClause SelectOptions
selectOptions)
          (SelectOptions -> Maybe LimitExpr
selectLimitExpr SelectOptions
selectOptions)
          (SelectOptions -> Maybe OffsetExpr
selectOffsetExpr SelectOptions
selectOptions)
    )