packages feed

orville-postgresql-1.1.0.0: src/Orville/PostgreSQL/Plan/Many.hs

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

@since 1.0.0.0
-}
module Orville.PostgreSQL.Plan.Many
  ( Many
  , NotAKey (NotAKey)
  , fromKeys
  , lookup
  , keys
  , elems
  , map
  , toMap
  , apply
  , compose
  )
where

import Prelude (Either (Left, Right), Functor (fmap), Maybe (Just, Nothing), Ord, ($), (.), (<*>))

import qualified Data.Either as Either
import qualified Data.Map as Map
import qualified Data.Maybe as Maybe

{- | 'NotAKey' is returned from various 'Many' related functions when presented
  with an input parameter that was not one of the original inputs that the
  'Many' was constructed with.

@since 1.0.0.0
-}
data NotAKey
  = NotAKey

{- | A 'Many k a' represents a group of values keyed by list of parameters and
  is used to return the results of executing an Orville Plan with a list of
  input parameters. If you need to find the result of the query associated
  with a particular input parameter, you can use 'lookup' to find it. If you
  don't care about the association with particular inputs, you can simply
  use 'elems' to get a list of all the results.

@since 1.0.0.0
-}
data Many k a
  = Many [k] (k -> Either NotAKey a)

-- | @since 1.0.0.0
instance Functor (Many k) where
  fmap = map

{- | 'fromKeys' constructs a 'Many' value from a list of keys and a function that
  maps them to their values. The order and duplication of keys in the list will
  be preserved by the 'Many' type in the relevant functions. The mapping
  function provided should be a total function -- i.e. it should not produce a
  runtime error. If it is not possible to map every @k@ (even those not in the
  input list provided to 'fromKeys'), the values should be wrapped in an
  appropriate type such as 'Maybe' so that an empty or default value can be
  returned.

@since 1.0.0.0
-}
fromKeys :: [k] -> (k -> Either NotAKey a) -> Many k a
fromKeys =
  Many

{- |  'map' calls a function on all the values found in a 'Many' collection.

@since 1.0.0.0
-}
map :: (a -> b) -> Many k a -> Many k b
map f (Many ks keyToValue) =
  Many ks (fmap f . keyToValue)

{- |  'apply' allows you to apply many functions to many values. The function
   associated with each parameter is applied to the value associated with the
   same paremeter.

   (If you're looking for 'Prelude.pure' or an 'Prelude.Applicative' instance
   for 'Many', this is as good as it gets. 'Many' cannot be an
   'Prelude.Applicative' because there is no correct implementation of
   'Prelude.pure' that we can reasonably provide).

@since 1.0.0.0
-}
apply ::
  (Many param (a -> b)) ->
  Many param a ->
  Many param b
apply manyFs manyAs =
  fromKeys (keys manyFs) applyF
 where
  applyF param =
    lookup param manyFs <*> lookup param manyAs

{- | 'compose' uses the values of a 'Many' value as keys to a second 'Many' to
  create a 'Many' mapping from the original keys to the final values.

@since 1.0.0.0
-}
compose :: Many b c -> Many a b -> Many a c
compose manyBC manyAB =
  fromKeys (keys manyAB) aToC
 where
  aToC a = do
    b <- lookup a manyAB
    lookup b manyBC

{- | 'keys' fetches the list of keys from a 'Many'. Note that is a list and not
  a set. 'Many' preserves the order and duplication of any key values that were
  in the key list at the time of construction.

@since 1.0.0.0
-}
keys :: Many k a -> [k]
keys (Many ks _) =
  ks

{- | 'elems' returns all the values that correspond to the keys of the 'Many'. The
  values will be returned in the same order that the keys were present at the
  time of creation, though if you truly care about this it's probably better to
  use 'lookup' to make that correspondence explicit.

@since 1.0.0.0
-}
elems :: Many k a -> [a]
elems (Many ks keyToValue) =
  Either.rights $ fmap keyToValue ks

{- | 'toMap' converts the 'Many' into a 'Map.Map' value. If all you wanted to do
  was find the value for a specific key, you should probably use 'lookup'
  instead.

@since 1.0.0.0
-}
toMap :: Ord k => Many k a -> Map.Map k a
toMap (Many ks keyToValue) =
  Map.fromList (Maybe.mapMaybe mkPair ks)
 where
  mkPair k =
    case keyToValue k of
      Left NotAKey ->
        Nothing
      Right value ->
        Just (k, value)

{- | 'lookup' returns the value for the given parameter. If the given @k@ is
  not one of the original input values that the 'Many' was constructed with,
  the mapping function given at the contructor will determine what value to
  return. Often this will be whatever a reasonable empty or default value for
  the type @a@ is.

@since 1.0.0.0
-}
lookup :: k -> Many k a -> Either NotAKey a
lookup k (Many _ keyToValue) =
  keyToValue k