packages feed

opaleye-0.9.3.3: src/Opaleye/Internal/Column.hs

{-# OPTIONS_HADDOCK not-home #-}

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}

module Opaleye.Internal.Column where

import Data.String

import qualified Opaleye.Internal.HaskellDB.PrimQuery as HPQ

data Nullability = NonNullable | Nullable

-- | A field of a @Select@, of type @sqlType@.  For example a @Field
-- SqlInt4@ is an @int4@ column and a @Field SqlText@ is a @text@
-- column.
newtype Field_ (n :: Nullability) sqlType = Column HPQ.PrimExpr

type Field = Field_ NonNullable
type FieldNullable = Field_ 'Nullable

-- | Only used within a 'Column', to indicate that it can be @NULL@.
-- For example, a 'Column' ('Nullable' @SqlText@) can be @NULL@ but a
-- 'Column' @SqlText@ cannot.
data Nullable a = Nullable_

-- | Do not use. Use 'Field' instead.  Will be removed in a later
-- version.
type family Column a where
  Column (Nullable a) = FieldNullable a
  Column a = Field a

unColumn :: Field_ n a -> HPQ.PrimExpr
unColumn (Column e) = e

-- | Treat a 'Column' as though it were of a different type.  If such
-- a treatment is not valid then Postgres may fail with an error at
-- SQL run time.
unsafeCoerceColumn :: Field_ n a -> Field_ n' b
unsafeCoerceColumn (Column e) = Column e

-- | Cast a column to any other type. Implements Postgres's @::@ or
-- @CAST( ... AS ... )@ operations.  This is safe for some
-- conversions, such as uuid to text.
unsafeCast :: String -> Field_ n a -> Field_ n b
unsafeCast = mapColumn . HPQ.CastExpr
  where
    mapColumn :: (HPQ.PrimExpr -> HPQ.PrimExpr) -> Field_ n c -> Field_ n' a
    mapColumn primExpr c = Column (primExpr (unColumn c))

unsafeCompositeField :: Field_ n a -> String -> Field_ n' b
unsafeCompositeField (Column e) fieldName =
  Column (HPQ.CompositeExpr e fieldName)

unsafeFromNullable :: Field_ n a
                   -> Field_ n' a
unsafeFromNullable (Column e) = Column e

binOp :: HPQ.BinOp -> Field_ n a -> Field_ n' b -> Field_ n'' c
binOp op (Column e) (Column e') = Column (HPQ.BinExpr op e e')

unOp :: HPQ.UnOp -> Field_ n a -> Field_ n' b
unOp op (Column e) = Column (HPQ.UnExpr op e)

-- For import order reasons we can't make the argument type SqlBool
unsafeCase_ :: [(Field_ n pgBool, Field_ n' a)] -> Field_ n' a -> Field_ n' a
unsafeCase_ alts (Column otherwise_) = Column (HPQ.CaseExpr (unColumns alts) otherwise_)
  where unColumns = map (\(Column e, Column e') -> (e, e'))

unsafeIfThenElse :: Field_ n' pgBool -> Field_ n a -> Field_ n a -> Field_ n a
unsafeIfThenElse cond t f = unsafeCase_ [(cond, t)] f

unsafeGt :: Field_ n a -> Field_ n a -> Field_ n' pgBool
unsafeGt = binOp (HPQ.:>)

unsafeEq :: Field_ n a -> Field_ n a -> Field_ n' pgBool
unsafeEq = binOp (HPQ.:==)

class SqlNum a where
  pgFromInteger :: Integer -> Field a
  pgFromInteger = sqlFromInteger

  sqlFromInteger :: Integer -> Field a

type PGNum = SqlNum

instance SqlNum a => Num (Field a) where
  fromInteger = pgFromInteger
  (*) = binOp (HPQ.:*)
  (+) = binOp (HPQ.:+)
  (-) = binOp (HPQ.:-)

  abs = unOp HPQ.OpAbs
  negate = unOp HPQ.OpNegate

  -- We can't use Postgres's 'sign' function because it returns only a
  -- numeric or a double
  signum c = unsafeCase_ [(c `unsafeGt` 0, 1), (c `unsafeEq` 0, 0)] (-1)

class SqlFractional a where
  pgFromRational :: Rational -> Field a
  pgFromRational = sqlFromRational

  sqlFromRational :: Rational -> Field a

type PGFractional = SqlFractional

instance (SqlNum a, SqlFractional a) => Fractional (Field a) where
  fromRational = sqlFromRational
  (/) = binOp (HPQ.:/)

-- | A dummy typeclass whose instances support integral operations.
class SqlIntegral a

type PGIntegral = SqlIntegral

class SqlString a where
    pgFromString :: String -> Field a
    pgFromString = sqlFromString

    sqlFromString :: String -> Field a

type PGString = SqlString

instance SqlString a => IsString (Field a) where
  fromString = sqlFromString