-- | Left, right, and full outer joins.

{-# LANGUAGE TypeFamilies          #-}

module Opaleye.Join where

import qualified Opaleye.Field               as F
import qualified Opaleye.Internal.Unpackspec as U
import qualified Opaleye.Internal.Join as J
import qualified Opaleye.Internal.MaybeFields as M
import qualified Opaleye.Internal.PrimQuery as PQ
import qualified Opaleye.Select   as S
import qualified Opaleye.SqlTypes as T

import qualified Data.Profunctor.Product.Default as D

-- * The recommended way of performing joins in Opaleye

-- $ref
--
-- Opaleye supports inner joins, left/right joins and full outer
-- joins.  Instead of using them directly we recommend the following,
-- which provide APIs that are more familiar to a Haskell programmer
-- and more composable:
--
-- - Inner joins: use 'Opaleye.Operators.where_' directly, along with
--   @do@ notation (or use 'Opaleye.Operators.restrict' directly,
--   along with arrow notation)
--
-- - Left/right joins: use 'optional'
--
-- We suspect the following do not have real world use cases.  If you
-- have one then we'd love to hear about it. Please [open a new issue
-- on the Opaleye
-- project](http://github.com/tomjaguarpaw/haskell-opaleye/issues/new)
-- and tell us about it.
--
-- - Left/right joins which really must not use @LATERAL@: use 'optionalRestrict'
--
-- - Full outer joins: use 'Opaleye.FunctionalJoin.fullJoinF'

-- | Convenient access to lateral left/right join
-- functionality. Performs a @LATERAL LEFT JOIN@ under the hood and
-- has behaviour equivalent to the following Haskell function:
--
-- @
-- optional :: [a] -> [Maybe a]
-- optional q = case q of
--     [] -> [Nothing]
--     xs -> map Just xs
-- @
--
-- That is, if @q :: 'SelectArr' i a@ returns no rows, @'optional' q
-- :: 'SelectArr' i ('MaybeFields' a)@ returns exactly one \"Nothing\"
-- row.  Otherwise, @'optional' q@ returns exactly the rows of @q@
-- wrapped in \"Just\".  For example,
--
-- @
-- > let l1 = ["one", "two", "three"] :: [Field SqlText]
-- > 'Opaleye.RunSelect.runSelectI' conn ('optional' ('Opaleye.Values.values' l1))
-- [Just "one", Just "two", Just "three"]
--
-- > let l2 = [] :: [Field SqlText]
-- > 'Opaleye.RunSelect.runSelectI' conn ('optional' ('Opaleye.Values.values' l2))
-- [Nothing]
-- @
--
-- 'optionalRestrict' is a special case of @optional@ and could be
-- written in terms of @optional@ as follows (except that
-- 'optionalRestrict' doesn't use @LATERAL@ under the hood and
-- @optional@ does).
--
-- @
-- optionalRestrict q = optional $ proc cond -> do
--   a <- q -< ()
--   restrict -< cond a
--   returnA -< a
-- @
optional :: D.Default U.Unpackspec a a
         => S.SelectArr i a
         -- ^ Input query
         -> S.SelectArr i (M.MaybeFields a)
         -- ^ The rows of the input query wrapped in \"Just\", unless
         -- the input query has no rows in which case a single row of
         -- \"Nothing\"
optional :: forall a i.
Default Unpackspec a a =>
SelectArr i a -> SelectArr i (MaybeFields a)
optional = SelectArr i a -> SelectArr i (MaybeFields a)
forall i a. SelectArr i a -> SelectArr i (MaybeFields a)
M.optional

-- | Convenient access to left/right join functionality.  Performs a
-- @LEFT JOIN@ under the hood and has behaviour equivalent to the
-- following Haskell function:
--
-- @
-- optionalRestrict :: [a] -> (a -> Bool) -> [Maybe a]
-- optionalRestrict xs p =
--    case filter p xs of []  -> [Nothing]
--                        xs' -> map Just xs'
-- @
--
-- For example,
--
-- @
-- > let l = [1, 10, 100, 1000] :: [Field SqlInt4]
-- > 'Opaleye.RunSelect.runSelectI' conn (proc () -> optionalRestrict ('Opaleye.Values.values' l) -\< (.> 100000))
-- [Nothing]
--
-- > 'Opaleye.RunSelect.runSelectI' conn (proc () -> optionalRestrict ('Opaleye.Values.values' l) -\< (.> 15))
-- [Just 100,Just 1000]
-- @
--
-- See the documentation of 'leftJoin' for how to use
-- 'optionalRestrict' to replace 'leftJoin' (and by symmetry,
-- 'rightJoin').
optionalRestrict :: D.Default U.Unpackspec a a
                 => S.Select a
                 -- ^ Input query
                 -> S.SelectArr (a -> F.Field T.SqlBool) (M.MaybeFields a)
                 -- ^ If any rows of the input query satisfy the
                 -- condition then return them (wrapped in \"Just\").
                 -- If none of them satisfy the condition then return a
                 -- single row of \"Nothing\"
optionalRestrict :: forall a.
Default Unpackspec a a =>
Select a -> SelectArr (a -> Field SqlBool) (MaybeFields a)
optionalRestrict = Select a -> SelectArr (a -> Field SqlBool) (MaybeFields a)
forall a.
Default Unpackspec a a =>
Select a -> SelectArr (a -> Field SqlBool) (MaybeFields a)
J.optionalRestrict

-- * Direct access to joins (not recommended)

-- $ref2
--
-- You probably want use the alternatives listed at the top of this
-- module instead of these.
-- The use of the @'D.Default' 'NullMaker'@ typeclass means that the compiler will
-- have trouble inferring types.  It is strongly recommended that you
-- provide full type signatures when using the join functions.
-- Example specialization:
--
-- @
-- leftJoin :: Select (Field a, Field b)
--          -> Select (Field c, FieldNullable d)
--          -> (((Field a, Field b), (Field c, FieldNullable d)) -> Field 'Opaleye.SqlTypes.SqlBool')
--          -> Select ((Field a, Field b), (FieldNullable c, FieldNullable d))
-- @

-- | We suggest you use 'optionalRestrict' instead.  Instead of writing
-- \"@'Opaleye.Join.leftJoin' qL qR cond@\" you can write
--
-- @
-- proc () -> do
--   fieldsL <- qL -< ()
--   maybeFieldsR \<- 'optionalRestrict' qR -\< 'Prelude.curry' cond fieldsL
--   'Control.Arrow.returnA' -< (fieldsL, maybeFieldsR)
-- @
--
-- Typically everything except the 'optionalRestrict' line can be
-- inlined in surrounding arrow notation.  In such cases, readability
-- and maintainability increase dramatically.
leftJoin  :: (D.Default U.Unpackspec fieldsL fieldsL,
              D.Default U.Unpackspec fieldsR fieldsR,
              D.Default J.NullMaker fieldsR nullableFieldsR)
          => S.Select fieldsL  -- ^ Left query
          -> S.Select fieldsR  -- ^ Right query
          -> ((fieldsL, fieldsR) -> F.Field T.SqlBool) -- ^ Condition on which to join
          -> S.Select (fieldsL, nullableFieldsR) -- ^ Left join
leftJoin :: forall fieldsL fieldsR nullableFieldsR.
(Default Unpackspec fieldsL fieldsL,
 Default Unpackspec fieldsR fieldsR,
 Default NullMaker fieldsR nullableFieldsR) =>
Select fieldsL
-> Select fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Select (fieldsL, nullableFieldsR)
leftJoin = Unpackspec fieldsL fieldsL
-> Unpackspec fieldsR fieldsR
-> NullMaker fieldsR nullableFieldsR
-> Select fieldsL
-> Select fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Select (fieldsL, nullableFieldsR)
forall fieldsL fieldsR nullableFieldsR.
Unpackspec fieldsL fieldsL
-> Unpackspec fieldsR fieldsR
-> NullMaker fieldsR nullableFieldsR
-> Select fieldsL
-> Select fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Select (fieldsL, nullableFieldsR)
leftJoinExplicit Unpackspec fieldsL fieldsL
forall (p :: * -> * -> *) a b. Default p a b => p a b
D.def Unpackspec fieldsR fieldsR
forall (p :: * -> * -> *) a b. Default p a b => p a b
D.def NullMaker fieldsR nullableFieldsR
forall (p :: * -> * -> *) a b. Default p a b => p a b
D.def

-- | We suggest you don't use this.  'optionalRestrict' is probably
-- better for your use case.  'Opaleye.Join.leftJoinA' is the same as
-- 'optionalRestrict' except without the return type wrapped in
-- 'Opaleye.Internal.MaybeFields.MaybeFields'.

leftJoinA :: (D.Default U.Unpackspec fieldsR fieldsR,
              D.Default J.NullMaker fieldsR nullableFieldsR)
          => S.Select fieldsR
          -- ^ Right query
          -> S.SelectArr (fieldsR -> F.Field T.SqlBool) nullableFieldsR
          -- ^ Condition on which to join goes in, left join
          -- result comes out
leftJoinA :: forall fieldsR nullableFieldsR.
(Default Unpackspec fieldsR fieldsR,
 Default NullMaker fieldsR nullableFieldsR) =>
Select fieldsR
-> SelectArr (fieldsR -> Field SqlBool) nullableFieldsR
leftJoinA = Unpackspec fieldsR fieldsR
-> NullMaker fieldsR nullableFieldsR
-> Select fieldsR
-> SelectArr (fieldsR -> Field SqlBool) nullableFieldsR
forall fieldsR nullableFieldsR.
Unpackspec fieldsR fieldsR
-> NullMaker fieldsR nullableFieldsR
-> Select fieldsR
-> SelectArr (fieldsR -> Field SqlBool) nullableFieldsR
leftJoinAExplict Unpackspec fieldsR fieldsR
forall (p :: * -> * -> *) a b. Default p a b => p a b
D.def NullMaker fieldsR nullableFieldsR
forall (p :: * -> * -> *) a b. Default p a b => p a b
D.def

-- | We suggest you use 'optionalRestrict' instead.  See 'leftJoin'
-- for more details.
rightJoin  :: (D.Default U.Unpackspec fieldsL fieldsL,
               D.Default U.Unpackspec fieldsR fieldsR,
               D.Default J.NullMaker fieldsL nullableFieldsL)
           => S.Select fieldsL -- ^ Left query
           -> S.Select fieldsR -- ^ Right query
           -> ((fieldsL, fieldsR) -> F.Field T.SqlBool) -- ^ Condition on which to join
           -> S.Select (nullableFieldsL, fieldsR) -- ^ Right join
rightJoin :: forall fieldsL fieldsR nullableFieldsL.
(Default Unpackspec fieldsL fieldsL,
 Default Unpackspec fieldsR fieldsR,
 Default NullMaker fieldsL nullableFieldsL) =>
Select fieldsL
-> Select fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Select (nullableFieldsL, fieldsR)
rightJoin = Unpackspec fieldsL fieldsL
-> Unpackspec fieldsR fieldsR
-> NullMaker fieldsL nullableFieldsL
-> Select fieldsL
-> Select fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Select (nullableFieldsL, fieldsR)
forall fieldsL fieldsR nullableFieldsL.
Unpackspec fieldsL fieldsL
-> Unpackspec fieldsR fieldsR
-> NullMaker fieldsL nullableFieldsL
-> Select fieldsL
-> Select fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Select (nullableFieldsL, fieldsR)
rightJoinExplicit Unpackspec fieldsL fieldsL
forall (p :: * -> * -> *) a b. Default p a b => p a b
D.def Unpackspec fieldsR fieldsR
forall (p :: * -> * -> *) a b. Default p a b => p a b
D.def NullMaker fieldsL nullableFieldsL
forall (p :: * -> * -> *) a b. Default p a b => p a b
D.def


fullJoin  :: (D.Default U.Unpackspec fieldsL fieldsL,
              D.Default U.Unpackspec fieldsR fieldsR,
              D.Default J.NullMaker fieldsL nullableFieldsL,
              D.Default J.NullMaker fieldsR nullableFieldsR)
          => S.Select fieldsL -- ^ Left query
          -> S.Select fieldsR -- ^ Right query
          -> ((fieldsL, fieldsR) -> F.Field T.SqlBool) -- ^ Condition on which to join
          -> S.Select (nullableFieldsL, nullableFieldsR) -- ^ Full outer join
fullJoin :: forall fieldsL fieldsR nullableFieldsL nullableFieldsR.
(Default Unpackspec fieldsL fieldsL,
 Default Unpackspec fieldsR fieldsR,
 Default NullMaker fieldsL nullableFieldsL,
 Default NullMaker fieldsR nullableFieldsR) =>
Select fieldsL
-> Select fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Select (nullableFieldsL, nullableFieldsR)
fullJoin = Unpackspec fieldsL fieldsL
-> Unpackspec fieldsR fieldsR
-> NullMaker fieldsL nullableFieldsL
-> NullMaker fieldsR nullableFieldsR
-> Select fieldsL
-> Select fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Select (nullableFieldsL, nullableFieldsR)
forall fieldsL fieldsR nullableFieldsL nullableFieldsR.
Unpackspec fieldsL fieldsL
-> Unpackspec fieldsR fieldsR
-> NullMaker fieldsL nullableFieldsL
-> NullMaker fieldsR nullableFieldsR
-> Select fieldsL
-> Select fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Select (nullableFieldsL, nullableFieldsR)
fullJoinExplicit Unpackspec fieldsL fieldsL
forall (p :: * -> * -> *) a b. Default p a b => p a b
D.def Unpackspec fieldsR fieldsR
forall (p :: * -> * -> *) a b. Default p a b => p a b
D.def NullMaker fieldsL nullableFieldsL
forall (p :: * -> * -> *) a b. Default p a b => p a b
D.def NullMaker fieldsR nullableFieldsR
forall (p :: * -> * -> *) a b. Default p a b => p a b
D.def

-- * Explicit versions

leftJoinExplicit :: U.Unpackspec fieldsL fieldsL
                 -> U.Unpackspec fieldsR fieldsR
                 -> J.NullMaker fieldsR nullableFieldsR
                 -> S.Select fieldsL -> S.Select fieldsR
                 -> ((fieldsL, fieldsR) -> F.Field T.SqlBool)
                 -> S.Select (fieldsL, nullableFieldsR)
leftJoinExplicit :: forall fieldsL fieldsR nullableFieldsR.
Unpackspec fieldsL fieldsL
-> Unpackspec fieldsR fieldsR
-> NullMaker fieldsR nullableFieldsR
-> Select fieldsL
-> Select fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Select (fieldsL, nullableFieldsR)
leftJoinExplicit Unpackspec fieldsL fieldsL
uA Unpackspec fieldsR fieldsR
uB NullMaker fieldsR nullableFieldsR
nullmaker =
  Unpackspec fieldsL fieldsL
-> Unpackspec fieldsR fieldsR
-> (fieldsL -> fieldsL)
-> (fieldsR -> nullableFieldsR)
-> JoinType
-> Query fieldsL
-> Query fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Query (fieldsL, nullableFieldsR)
forall columnsA columnsB returnedColumnsA returnedColumnsB.
Unpackspec columnsA columnsA
-> Unpackspec columnsB columnsB
-> (columnsA -> returnedColumnsA)
-> (columnsB -> returnedColumnsB)
-> JoinType
-> Query columnsA
-> Query columnsB
-> ((columnsA, columnsB) -> Field SqlBool)
-> Query (returnedColumnsA, returnedColumnsB)
J.joinExplicit Unpackspec fieldsL fieldsL
uA Unpackspec fieldsR fieldsR
uB fieldsL -> fieldsL
forall a. a -> a
id (NullMaker fieldsR nullableFieldsR -> fieldsR -> nullableFieldsR
forall a b. NullMaker a b -> a -> b
J.toNullable NullMaker fieldsR nullableFieldsR
nullmaker) JoinType
PQ.LeftJoin

leftJoinAExplict :: U.Unpackspec fieldsR fieldsR
                 -> J.NullMaker fieldsR nullableFieldsR
                 -> S.Select fieldsR
                 -> S.SelectArr (fieldsR -> F.Field T.SqlBool) nullableFieldsR
leftJoinAExplict :: forall fieldsR nullableFieldsR.
Unpackspec fieldsR fieldsR
-> NullMaker fieldsR nullableFieldsR
-> Select fieldsR
-> SelectArr (fieldsR -> Field SqlBool) nullableFieldsR
leftJoinAExplict = Unpackspec fieldsR fieldsR
-> NullMaker fieldsR nullableFieldsR
-> Query fieldsR
-> QueryArr (fieldsR -> Field SqlBool) nullableFieldsR
forall fieldsR nullableFieldsR.
Unpackspec fieldsR fieldsR
-> NullMaker fieldsR nullableFieldsR
-> Select fieldsR
-> SelectArr (fieldsR -> Field SqlBool) nullableFieldsR
J.leftJoinAExplicit

rightJoinExplicit :: U.Unpackspec fieldsL fieldsL
                  -> U.Unpackspec fieldsR fieldsR
                  -> J.NullMaker fieldsL nullableFieldsL
                  -> S.Select fieldsL -> S.Select fieldsR
                  -> ((fieldsL, fieldsR) -> F.Field T.SqlBool)
                  -> S.Select (nullableFieldsL, fieldsR)
rightJoinExplicit :: forall fieldsL fieldsR nullableFieldsL.
Unpackspec fieldsL fieldsL
-> Unpackspec fieldsR fieldsR
-> NullMaker fieldsL nullableFieldsL
-> Select fieldsL
-> Select fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Select (nullableFieldsL, fieldsR)
rightJoinExplicit Unpackspec fieldsL fieldsL
uA Unpackspec fieldsR fieldsR
uB NullMaker fieldsL nullableFieldsL
nullmaker =
  Unpackspec fieldsL fieldsL
-> Unpackspec fieldsR fieldsR
-> (fieldsL -> nullableFieldsL)
-> (fieldsR -> fieldsR)
-> JoinType
-> Query fieldsL
-> Query fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Query (nullableFieldsL, fieldsR)
forall columnsA columnsB returnedColumnsA returnedColumnsB.
Unpackspec columnsA columnsA
-> Unpackspec columnsB columnsB
-> (columnsA -> returnedColumnsA)
-> (columnsB -> returnedColumnsB)
-> JoinType
-> Query columnsA
-> Query columnsB
-> ((columnsA, columnsB) -> Field SqlBool)
-> Query (returnedColumnsA, returnedColumnsB)
J.joinExplicit Unpackspec fieldsL fieldsL
uA Unpackspec fieldsR fieldsR
uB (NullMaker fieldsL nullableFieldsL -> fieldsL -> nullableFieldsL
forall a b. NullMaker a b -> a -> b
J.toNullable NullMaker fieldsL nullableFieldsL
nullmaker) fieldsR -> fieldsR
forall a. a -> a
id JoinType
PQ.RightJoin


fullJoinExplicit :: U.Unpackspec fieldsL fieldsL
                 -> U.Unpackspec fieldsR fieldsR
                 -> J.NullMaker fieldsL nullableFieldsL
                 -> J.NullMaker fieldsR nullableFieldsR
                 -> S.Select fieldsL -> S.Select fieldsR
                 -> ((fieldsL, fieldsR) -> F.Field T.SqlBool)
                 -> S.Select (nullableFieldsL, nullableFieldsR)
fullJoinExplicit :: forall fieldsL fieldsR nullableFieldsL nullableFieldsR.
Unpackspec fieldsL fieldsL
-> Unpackspec fieldsR fieldsR
-> NullMaker fieldsL nullableFieldsL
-> NullMaker fieldsR nullableFieldsR
-> Select fieldsL
-> Select fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Select (nullableFieldsL, nullableFieldsR)
fullJoinExplicit Unpackspec fieldsL fieldsL
uA Unpackspec fieldsR fieldsR
uB NullMaker fieldsL nullableFieldsL
nullmakerA NullMaker fieldsR nullableFieldsR
nullmakerB =
  Unpackspec fieldsL fieldsL
-> Unpackspec fieldsR fieldsR
-> (fieldsL -> nullableFieldsL)
-> (fieldsR -> nullableFieldsR)
-> JoinType
-> Query fieldsL
-> Query fieldsR
-> ((fieldsL, fieldsR) -> Field SqlBool)
-> Query (nullableFieldsL, nullableFieldsR)
forall columnsA columnsB returnedColumnsA returnedColumnsB.
Unpackspec columnsA columnsA
-> Unpackspec columnsB columnsB
-> (columnsA -> returnedColumnsA)
-> (columnsB -> returnedColumnsB)
-> JoinType
-> Query columnsA
-> Query columnsB
-> ((columnsA, columnsB) -> Field SqlBool)
-> Query (returnedColumnsA, returnedColumnsB)
J.joinExplicit Unpackspec fieldsL fieldsL
uA Unpackspec fieldsR fieldsR
uB (NullMaker fieldsL nullableFieldsL -> fieldsL -> nullableFieldsL
forall a b. NullMaker a b -> a -> b
J.toNullable NullMaker fieldsL nullableFieldsL
nullmakerA) (NullMaker fieldsR nullableFieldsR -> fieldsR -> nullableFieldsR
forall a b. NullMaker a b -> a -> b
J.toNullable NullMaker fieldsR nullableFieldsR
nullmakerB) JoinType
PQ.FullJoin

optionalRestrictExplicit :: U.Unpackspec a a
                         -> S.Select a
                         -> S.SelectArr (a -> F.Field T.SqlBool) (M.MaybeFields a)
optionalRestrictExplicit :: forall a.
Unpackspec a a
-> Select a -> SelectArr (a -> Field SqlBool) (MaybeFields a)
optionalRestrictExplicit = Unpackspec a a
-> Select a -> SelectArr (a -> Field SqlBool) (MaybeFields a)
forall a.
Unpackspec a a
-> Select a -> SelectArr (a -> Field SqlBool) (MaybeFields a)
J.optionalRestrictExplicit

-- The Unpackpec is not used but I'm adding it in case we discover we
-- need it in the future.  Then we can use it without breaking the
-- API.
optionalExplicit :: U.Unpackspec a a
                 -> S.SelectArr i a
                 -> S.SelectArr i (M.MaybeFields a)
optionalExplicit :: forall a i.
Unpackspec a a -> SelectArr i a -> SelectArr i (MaybeFields a)
optionalExplicit Unpackspec a a
_ = SelectArr i a -> SelectArr i (MaybeFields a)
forall i a. SelectArr i a -> SelectArr i (MaybeFields a)
M.optional