rel8-1.2.2.0: src/Rel8/Type.hs
{-# language FlexibleContexts #-}
{-# language FlexibleInstances #-}
{-# language MonoLocalBinds #-}
{-# language MultiWayIf #-}
{-# language StandaloneKindSignatures #-}
{-# language UndecidableInstances #-}
module Rel8.Type
( DBType (typeInformation)
)
where
-- aeson
import Data.Aeson ( Value )
import qualified Data.Aeson as Aeson
-- base
import Data.Int ( Int16, Int32, Int64 )
import Data.List.NonEmpty ( NonEmpty )
import Data.Kind ( Constraint, Type )
import Prelude
-- bytestring
import Data.ByteString ( ByteString )
import qualified Data.ByteString.Lazy as Lazy ( ByteString )
import qualified Data.ByteString.Lazy as ByteString ( fromStrict, toStrict )
-- case-insensitive
import Data.CaseInsensitive ( CI )
import qualified Data.CaseInsensitive as CI
-- hasql
import qualified Hasql.Decoders as Hasql
-- opaleye
import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye
import qualified Opaleye.Internal.HaskellDB.Sql.Default as Opaleye ( quote )
-- rel8
import Rel8.Schema.Null ( NotNull, Sql, nullable )
import Rel8.Type.Array ( listTypeInformation, nonEmptyTypeInformation )
import Rel8.Type.Information ( TypeInformation(..), mapTypeInformation )
-- scientific
import Data.Scientific ( Scientific )
-- text
import Data.Text ( Text )
import qualified Data.Text as Text
import qualified Data.Text.Lazy as Lazy ( Text, unpack )
import qualified Data.Text.Lazy as Text ( fromStrict, toStrict )
import qualified Data.Text.Lazy.Encoding as Lazy ( decodeUtf8 )
-- time
import Data.Time.Calendar ( Day )
import Data.Time.Clock ( UTCTime )
import Data.Time.LocalTime
( CalendarDiffTime( CalendarDiffTime )
, LocalTime
, TimeOfDay
)
import Data.Time.Format ( formatTime, defaultTimeLocale )
-- uuid
import Data.UUID ( UUID )
import qualified Data.UUID as UUID
-- | Haskell types that can be represented as expressions in a database. There
-- should be an instance of @DBType@ for all column types in your database
-- schema (e.g., @int@, @timestamptz@, etc).
--
-- Rel8 comes with stock instances for most default types in PostgreSQL, so you
-- should only need to derive instances of this class for custom database
-- types, such as types defined in PostgreSQL extensions, or custom domain
-- types.
type DBType :: Type -> Constraint
class NotNull a => DBType a where
typeInformation :: TypeInformation a
-- | Corresponds to @bool@
instance DBType Bool where
typeInformation = TypeInformation
{ encode = Opaleye.ConstExpr . Opaleye.BoolLit
, decode = Hasql.bool
, typeName = "bool"
}
-- | Corresponds to @char@
instance DBType Char where
typeInformation = TypeInformation
{ encode = Opaleye.ConstExpr . Opaleye.StringLit . pure
, decode = Hasql.char
, typeName = "char"
}
-- | Corresponds to @int2@
instance DBType Int16 where
typeInformation = TypeInformation
{ encode = Opaleye.ConstExpr . Opaleye.IntegerLit . toInteger
, decode = Hasql.int2
, typeName = "int2"
}
-- | Corresponds to @int4@
instance DBType Int32 where
typeInformation = TypeInformation
{ encode = Opaleye.ConstExpr . Opaleye.IntegerLit . toInteger
, decode = Hasql.int4
, typeName = "int4"
}
-- | Corresponds to @int8@
instance DBType Int64 where
typeInformation = TypeInformation
{ encode = Opaleye.ConstExpr . Opaleye.IntegerLit . toInteger
, decode = Hasql.int8
, typeName = "int8"
}
-- | Corresponds to @float4@
instance DBType Float where
typeInformation = TypeInformation
{ encode = \x -> Opaleye.ConstExpr
if | x == (1 /0) -> Opaleye.OtherLit "'Infinity'"
| isNaN x -> Opaleye.OtherLit "'NaN'"
| x == (-1/0) -> Opaleye.OtherLit "'-Infinity'"
| otherwise -> Opaleye.NumericLit $ realToFrac x
, decode = Hasql.float4
, typeName = "float4"
}
-- | Corresponds to @float8@
instance DBType Double where
typeInformation = TypeInformation
{ encode = \x -> Opaleye.ConstExpr
if | x == (1 /0) -> Opaleye.OtherLit "'Infinity'"
| isNaN x -> Opaleye.OtherLit "'NaN'"
| x == (-1/0) -> Opaleye.OtherLit "'-Infinity'"
| otherwise -> Opaleye.NumericLit $ realToFrac x
, decode = Hasql.float8
, typeName = "float8"
}
-- | Corresponds to @numeric@
instance DBType Scientific where
typeInformation = TypeInformation
{ encode = Opaleye.ConstExpr . Opaleye.NumericLit
, decode = Hasql.numeric
, typeName = "numeric"
}
-- | Corresponds to @timestamptz@
instance DBType UTCTime where
typeInformation = TypeInformation
{ encode =
Opaleye.ConstExpr . Opaleye.OtherLit .
formatTime defaultTimeLocale "'%FT%T%QZ'"
, decode = Hasql.timestamptz
, typeName = "timestamptz"
}
-- | Corresponds to @date@
instance DBType Day where
typeInformation = TypeInformation
{ encode =
Opaleye.ConstExpr . Opaleye.OtherLit .
formatTime defaultTimeLocale "'%F'"
, decode = Hasql.date
, typeName = "date"
}
-- | Corresponds to @timestamp@
instance DBType LocalTime where
typeInformation = TypeInformation
{ encode =
Opaleye.ConstExpr . Opaleye.OtherLit .
formatTime defaultTimeLocale "'%FT%T%Q'"
, decode = Hasql.timestamp
, typeName = "timestamp"
}
-- | Corresponds to @time@
instance DBType TimeOfDay where
typeInformation = TypeInformation
{ encode =
Opaleye.ConstExpr . Opaleye.OtherLit .
formatTime defaultTimeLocale "'%T%Q'"
, decode = Hasql.time
, typeName = "time"
}
-- | Corresponds to @interval@
instance DBType CalendarDiffTime where
typeInformation = TypeInformation
{ encode =
Opaleye.ConstExpr . Opaleye.OtherLit .
formatTime defaultTimeLocale "'%bmon %0Es'"
, decode = CalendarDiffTime 0 . realToFrac <$> Hasql.interval
, typeName = "interval"
}
-- | Corresponds to @text@
instance DBType Text where
typeInformation = TypeInformation
{ encode = Opaleye.ConstExpr . Opaleye.StringLit . Text.unpack
, decode = Hasql.text
, typeName = "text"
}
-- | Corresponds to @text@
instance DBType Lazy.Text where
typeInformation =
mapTypeInformation Text.fromStrict Text.toStrict typeInformation
-- | Corresponds to @citext@
instance DBType (CI Text) where
typeInformation = mapTypeInformation CI.mk CI.original typeInformation
{ typeName = "citext"
}
-- | Corresponds to @citext@
instance DBType (CI Lazy.Text) where
typeInformation = mapTypeInformation CI.mk CI.original typeInformation
{ typeName = "citext"
}
-- | Corresponds to @bytea@
instance DBType ByteString where
typeInformation = TypeInformation
{ encode = Opaleye.ConstExpr . Opaleye.ByteStringLit
, decode = Hasql.bytea
, typeName = "bytea"
}
-- | Corresponds to @bytea@
instance DBType Lazy.ByteString where
typeInformation =
mapTypeInformation ByteString.fromStrict ByteString.toStrict
typeInformation
-- | Corresponds to @uuid@
instance DBType UUID where
typeInformation = TypeInformation
{ encode = Opaleye.ConstExpr . Opaleye.StringLit . UUID.toString
, decode = Hasql.uuid
, typeName = "uuid"
}
-- | Corresponds to @jsonb@
instance DBType Value where
typeInformation = TypeInformation
{ encode =
Opaleye.ConstExpr . Opaleye.OtherLit .
Opaleye.quote .
Lazy.unpack . Lazy.decodeUtf8 . Aeson.encode
, decode = Hasql.jsonb
, typeName = "jsonb"
}
instance Sql DBType a => DBType [a] where
typeInformation = listTypeInformation nullable typeInformation
instance Sql DBType a => DBType (NonEmpty a) where
typeInformation = nonEmptyTypeInformation nullable typeInformation