packages feed

rel8-1.5.0.0: src/Rel8/Table/NonEmpty.hs

{-# language DataKinds #-}
{-# language FlexibleContexts #-}
{-# language FlexibleInstances #-}
{-# language MultiParamTypeClasses #-}
{-# language NamedFieldPuns #-}
{-# language ScopedTypeVariables #-}
{-# language StandaloneKindSignatures #-}
{-# language TypeApplications #-}
{-# language TypeFamilies #-}
{-# language TypeOperators #-}
{-# language UndecidableInstances #-}

module Rel8.Table.NonEmpty
  ( NonEmptyTable(..)
  , ($+)
  , nonEmptyTable
  , nameNonEmptyTable
  , head1
  , index1
  , last1
  , length1
  )
where

-- base
import Data.Functor.Identity (Identity (Identity), runIdentity)
import Data.Int (Int32)
import Data.Kind ( Type )
import Data.List.NonEmpty ( NonEmpty )
import Prelude hiding ( id )

-- rel8
import Rel8.Expr ( Expr )
import Rel8.Expr.Array ( sappend1, snonEmptyOf )
import Rel8.Expr.NonEmpty (length1Expr, shead1Expr, sindex1Expr, slast1Expr)
import Rel8.Schema.Dict ( Dict( Dict ) )
import Rel8.Schema.HTable.NonEmpty ( HNonEmptyTable )
import Rel8.Schema.HTable.Vectorize
  ( hvectorize, hunvectorize
  , hnullify
  , happend
  , hproject, hcolumn
  , First (..)
  )
import qualified Rel8.Schema.Kind as K
import Rel8.Schema.Name ( Name( Name ) )
import Rel8.Schema.Null ( Nullity( Null, NotNull ) )
import Rel8.Schema.Result ( vectorizer, unvectorizer )
import Rel8.Schema.Spec ( Spec(..) )
import Rel8.Table
  ( Table, Context, Columns, fromColumns, toColumns
  , FromExprs, fromResult, toResult
  , Transpose
  )
import Rel8.Table.Alternative ( AltTable, (<|>:) )
import Rel8.Table.Eq ( EqTable, eqTable )
import Rel8.Table.Null (NullTable)
import Rel8.Table.Ord ( OrdTable, ordTable )
import Rel8.Table.Projection
  ( Projectable, Projecting, Projection, project, apply
  )
import Rel8.Table.Serialize ( ToExprs )


-- | A @NonEmptyTable@ value contains one or more instances of @a@. You
-- construct @NonEmptyTable@s with 'Rel8.some' or 'nonEmptyAgg'.
type NonEmptyTable :: K.Context -> Type -> Type
newtype NonEmptyTable context a =
  NonEmptyTable (HNonEmptyTable (Columns a) (Context a))


instance Projectable (NonEmptyTable context) where
  project f (NonEmptyTable a) = NonEmptyTable (hproject (apply f) a)


instance (Table context a, context ~ context') =>
  Table context' (NonEmptyTable context a)
 where
  type Columns (NonEmptyTable context a) = HNonEmptyTable (Columns a)
  type Context (NonEmptyTable context a) = Context a
  type FromExprs (NonEmptyTable context a) = NonEmpty (FromExprs a)
  type Transpose to (NonEmptyTable context a) =
    NonEmptyTable to (Transpose to a)

  fromColumns = NonEmptyTable
  toColumns (NonEmptyTable a) = a
  fromResult = fmap (fromResult @_ @a) . hunvectorize unvectorizer
  toResult = hvectorize vectorizer . fmap (toResult @_ @a)


instance (EqTable a, context ~ Expr) =>
  EqTable (NonEmptyTable context a)
 where
  eqTable =
    hvectorize
      (\Spec {nullity} (Identity Dict) -> case nullity of
        Null -> Dict
        NotNull -> Dict)
      (Identity (eqTable @a))


instance (OrdTable a, context ~ Expr) =>
  OrdTable (NonEmptyTable context a)
 where
  ordTable =
    hvectorize
      (\Spec {nullity} (Identity Dict) -> case nullity of
        Null -> Dict
        NotNull -> Dict)
      (Identity (ordTable @a))


instance (ToExprs exprs a, context ~ Expr) =>
  ToExprs (NonEmptyTable context exprs) (NonEmpty a)


instance context ~ Expr => AltTable (NonEmptyTable context) where
  (<|>:) = (<>)


instance (Table Expr a, context ~ Expr) => Semigroup (NonEmptyTable context a)
 where
  NonEmptyTable as <> NonEmptyTable bs = NonEmptyTable $
    happend (const sappend1) as bs


-- | Project a single expression out of a 'NonEmptyTable'.
($+) :: Projecting a (Expr b)
  => Projection a (Expr b) -> NonEmptyTable Expr a -> Expr (NonEmpty b)
f $+ NonEmptyTable a = hcolumn $ hproject (apply f) a
infixl 4 $+


-- | Construct a @NonEmptyTable@ from a non-empty list of expressions.
nonEmptyTable :: Table Expr a => NonEmpty a -> NonEmptyTable Expr a
nonEmptyTable =
  NonEmptyTable .
  hvectorize (\Spec {info} -> snonEmptyOf info) .
  fmap toColumns


-- | Construct a 'NonEmptyTable' in the 'Name' context. This can be useful if
-- you have a 'NonEmptyTable' that you are storing in a table and need to
-- construct a 'TableSchema'.
nameNonEmptyTable
  :: Table Name a
  => a -- ^ The names of the columns of elements of the list.
  -> NonEmptyTable Name a
nameNonEmptyTable =
  NonEmptyTable .
  hvectorize (\_ (Identity (Name a)) -> Name a) .
  pure .
  toColumns


-- | Get the first element of a 'NonEmptyTable'.
head1 :: Table Expr a => NonEmptyTable Expr a -> a
head1 =
  fromColumns .
  runIdentity .
  hunvectorize (\Spec {info} -> Identity . shead1Expr info) .
  toColumns


-- | @'index1' i as@ extracts a single element from @as@, returning
-- 'Rel8.nullTable' if @i@ is out of range. Note that although PostgreSQL
-- array indexes are 1-based (by default), this function is always 0-based.
index1 :: Table Expr a => Expr Int32 -> NonEmptyTable Expr a -> NullTable Expr a
index1 i =
  fromColumns .
  hnullify (\Spec {info} -> sindex1Expr info i) .
  toColumns


-- | Get the last element of a 'NonEmptyTable'.
last1 :: Table Expr a => NonEmptyTable Expr a -> a
last1 =
  fromColumns .
  runIdentity .
  hunvectorize (\Spec {info} -> Identity . slast1Expr info) .
  toColumns


-- | Get the length of a 'NonEmptyTable'
length1 :: Table Expr a => NonEmptyTable Expr a -> Expr Int32
length1 =
  getFirst .
  hunvectorize (\_ -> First . length1Expr) .
  toColumns