{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}

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

This module provides functions for working with Orville 'FieldDefinition'
values. 'FieldDefinition' is use to determine the column name and data type
that a Haskell field is mapped to via a
'Orville.PostgreSQL.Marhall.SqlMarshaller'. It is also used for constructing
boolean conditions for matching rows in queries.

@since 1.0.0.0
-}
module Orville.PostgreSQL.Marshall.FieldDefinition
  ( FieldDefinition
  , fieldName
  , setFieldName
  , fieldDescription
  , setFieldDescription
  , fieldType
  , fieldIsNotNullable
  , fieldDefaultValue
  , fieldNullability
  , fieldTableConstraints
  , addFieldTableConstraints
  , addForeignKeyConstraint
  , addForeignKeyConstraintWithOptions
  , addUniqueConstraint
  , fieldEquals
  , (.==)
  , fieldNotEquals
  , (./=)
  , fieldGreaterThan
  , (.>)
  , fieldLessThan
  , (.<)
  , fieldGreaterThanOrEqualTo
  , (.>=)
  , fieldLessThanOrEqualTo
  , (.<=)
  , fieldIsNull
  , fieldIsNotNull
  , fieldLike
  , fieldLikeInsensitive
  , fieldIn
  , (.<-)
  , fieldNotIn
  , (.</-)
  , fieldTupleIn
  , fieldTupleNotIn
  , setField
  , (.:=)
  , orderByField
  , FieldNullability (..)
  , fieldValueToExpression
  , fieldValueToSqlValue
  , fieldValueFromSqlValue
  , fieldColumnName
  , fieldColumnReference
  , fieldColumnDefinition
  , FieldName
  , stringToFieldName
  , fieldNameToString
  , fieldNameToColumnName
  , fieldNameToByteString
  , byteStringToFieldName
  , NotNull
  , Nullable
  , convertField
  , coerceField
  , nullableField
  , asymmetricNullableField
  , setDefaultValue
  , removeDefaultValue
  , prefixField
  , integerField
  , serialField
  , smallIntegerField
  , bigIntegerField
  , bigSerialField
  , doubleField
  , booleanField
  , unboundedTextField
  , boundedTextField
  , fixedTextField
  , textSearchVectorField
  , dateField
  , utcTimestampField
  , localTimestampField
  , uuidField
  , jsonbField
  , fieldOfType
  , whereColumnComparison
  )
where

import qualified Data.ByteString.Char8 as B8
import qualified Data.Coerce as Coerce
import Data.Int (Int16, Int32, Int64)
import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.Text as T
import qualified Data.Time as Time
import qualified Data.UUID as UUID

import qualified Orville.PostgreSQL.Expr as Expr
import Orville.PostgreSQL.Internal.FieldName (FieldName, byteStringToFieldName, fieldNameToByteString, fieldNameToColumnName, fieldNameToString, stringToFieldName)
import qualified Orville.PostgreSQL.Marshall.DefaultValue as DefaultValue
import qualified Orville.PostgreSQL.Marshall.SqlType as SqlType
import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue
import qualified Orville.PostgreSQL.Schema.ConstraintDefinition as ConstraintDefinition
import qualified Orville.PostgreSQL.Schema.TableIdentifier as TableIdentifier

{- |
  'FieldDefinition' determines the SQL construction of a column in the
  database, comprising the name, SQL type and whether the field is nullable.
  A 'FieldDefinition' is matched to a particular Haskell type, which it knows
  how to marshall to and from the database representation of SQL type for
  the field.

@since 1.0.0.0
-}
data FieldDefinition nullability a = FieldDefinition
  { forall nullability a. FieldDefinition nullability a -> FieldName
i_fieldName :: FieldName
  , forall nullability a. FieldDefinition nullability a -> SqlType a
i_fieldType :: SqlType.SqlType a
  , forall nullability a.
FieldDefinition nullability a -> NullabilityGADT nullability
i_fieldNullability :: NullabilityGADT nullability
  , forall nullability a.
FieldDefinition nullability a -> Maybe (DefaultValue a)
i_fieldDefaultValue :: Maybe (DefaultValue.DefaultValue a)
  , forall nullability a. FieldDefinition nullability a -> Maybe String
i_fieldDescription :: Maybe String
  , forall nullability a.
FieldDefinition nullability a
-> [FieldName -> ConstraintDefinition]
i_fieldTableConstraints :: [FieldName -> ConstraintDefinition.ConstraintDefinition]
  }

{- |
  The name used in database queries to reference the field.

@since 1.0.0.0
-}
fieldName :: FieldDefinition nullability a -> FieldName
fieldName :: forall nullability a. FieldDefinition nullability a -> FieldName
fieldName = FieldDefinition nullability a -> FieldName
forall nullability a. FieldDefinition nullability a -> FieldName
i_fieldName

{- |
  Sets the name used in database queries to reference the field.

@since 1.0.0.0
-}
setFieldName :: FieldName -> FieldDefinition nullability a -> FieldDefinition nullability a
setFieldName :: forall nullability a.
FieldName
-> FieldDefinition nullability a -> FieldDefinition nullability a
setFieldName FieldName
newName FieldDefinition nullability a
fieldDef =
  FieldDefinition nullability a
fieldDef
    { i_fieldName :: FieldName
i_fieldName = FieldName
newName
    }

{- |
  Returns the description that was passed to 'setFieldDescription', if any.

@since 1.0.0.0
-}
fieldDescription :: FieldDefinition nullability a -> Maybe String
fieldDescription :: forall nullability a. FieldDefinition nullability a -> Maybe String
fieldDescription = FieldDefinition nullability a -> Maybe String
forall nullability a. FieldDefinition nullability a -> Maybe String
i_fieldDescription

{- |
  Sets the description for the field. This description is not currently used
  anywhere by Orville itself, but users can retrieve the description via
  'fieldDescription' for their own purposes (e.g. generating documentation).

@since 1.0.0.0
-}
setFieldDescription :: String -> FieldDefinition nullability a -> FieldDefinition nullability a
setFieldDescription :: forall nullability a.
String
-> FieldDefinition nullability a -> FieldDefinition nullability a
setFieldDescription String
description FieldDefinition nullability a
fieldDef =
  FieldDefinition nullability a
fieldDef
    { i_fieldDescription :: Maybe String
i_fieldDescription = String -> Maybe String
forall a. a -> Maybe a
Just String
description
    }

{- |
  The 'SqlType.SqlType' for the 'FieldDefinition' determines the PostgreSQL
  data type used to define the field as well as how to marshall Haskell values
  to and from the database.

@since 1.0.0.0
-}
fieldType :: FieldDefinition nullability a -> SqlType.SqlType a
fieldType :: forall nullability a. FieldDefinition nullability a -> SqlType a
fieldType = FieldDefinition nullability a -> SqlType a
forall nullability a. FieldDefinition nullability a -> SqlType a
i_fieldType

{- |
  Returns the default value definition for the field, if any has been set.

@since 1.0.0.0
-}
fieldDefaultValue :: FieldDefinition nullability a -> Maybe (DefaultValue.DefaultValue a)
fieldDefaultValue :: forall nullability a.
FieldDefinition nullability a -> Maybe (DefaultValue a)
fieldDefaultValue = FieldDefinition nullability a -> Maybe (DefaultValue a)
forall nullability a.
FieldDefinition nullability a -> Maybe (DefaultValue a)
i_fieldDefaultValue

{- |
 A 'FieldNullability' is returned by the 'fieldNullability' function, which
 can be used when a function works on both 'Nullable' and 'NotNull' functions
 but needs to deal with each type of field separately. It adds wrapper
 constructors around the 'FieldDefinition' that you can pattern match on to
 then work with a concrete 'Nullable' or 'NotNull' field.

@since 1.0.0.0
-}
data FieldNullability a
  = NullableField (FieldDefinition Nullable a)
  | NotNullField (FieldDefinition NotNull a)

{- |
 Resolves the @nullability@ of a field to a concrete type, which is returned
 via the 'FieldNullability' type. You can pattern match on this type to then
 extract the either 'Nullable' or 'NotNull' field for cases where you may
 require different logic based on the nullability of a field.

@since 1.0.0.0
-}
fieldNullability :: FieldDefinition nullability a -> FieldNullability a
fieldNullability :: forall nullability a.
FieldDefinition nullability a -> FieldNullability a
fieldNullability FieldDefinition nullability a
field =
  case FieldDefinition nullability a -> NullabilityGADT nullability
forall nullability a.
FieldDefinition nullability a -> NullabilityGADT nullability
i_fieldNullability FieldDefinition nullability a
field of
    NullabilityGADT nullability
NullableGADT -> FieldDefinition Nullable a -> FieldNullability a
forall a. FieldDefinition Nullable a -> FieldNullability a
NullableField FieldDefinition nullability a
FieldDefinition Nullable a
field
    NullabilityGADT nullability
NotNullGADT -> FieldDefinition NotNull a -> FieldNullability a
forall a. FieldDefinition NotNull a -> FieldNullability a
NotNullField FieldDefinition nullability a
FieldDefinition NotNull a
field

{- |
  Indicates whether a field is not nullable.

@since 1.0.0.0
-}
fieldIsNotNullable :: FieldDefinition nullability a -> Bool
fieldIsNotNullable :: forall nullability a. FieldDefinition nullability a -> Bool
fieldIsNotNullable FieldDefinition nullability a
field =
  case FieldDefinition nullability a -> NullabilityGADT nullability
forall nullability a.
FieldDefinition nullability a -> NullabilityGADT nullability
i_fieldNullability FieldDefinition nullability a
field of
    NullabilityGADT nullability
NullableGADT -> Bool
False
    NullabilityGADT nullability
NotNullGADT -> Bool
True

{- |
  A list of table constraints that will be included on any table that uses this
  field definition.

@since 1.0.0.0
-}
fieldTableConstraints ::
  FieldDefinition nullability a ->
  ConstraintDefinition.TableConstraints
fieldTableConstraints :: forall nullability a.
FieldDefinition nullability a -> TableConstraints
fieldTableConstraints FieldDefinition nullability a
fieldDef =
  let
    name :: FieldName
name =
      FieldDefinition nullability a -> FieldName
forall nullability a. FieldDefinition nullability a -> FieldName
fieldName FieldDefinition nullability a
fieldDef

    constructedConstraints :: [ConstraintDefinition]
constructedConstraints =
      ((FieldName -> ConstraintDefinition) -> ConstraintDefinition)
-> [FieldName -> ConstraintDefinition] -> [ConstraintDefinition]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((FieldName -> ConstraintDefinition)
-> FieldName -> ConstraintDefinition
forall a b. (a -> b) -> a -> b
$ FieldName
name) (FieldDefinition nullability a
-> [FieldName -> ConstraintDefinition]
forall nullability a.
FieldDefinition nullability a
-> [FieldName -> ConstraintDefinition]
i_fieldTableConstraints FieldDefinition nullability a
fieldDef)
  in
    (ConstraintDefinition -> TableConstraints -> TableConstraints)
-> TableConstraints -> [ConstraintDefinition] -> TableConstraints
forall a b. (a -> b -> b) -> b -> [a] -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr
      ConstraintDefinition -> TableConstraints -> TableConstraints
ConstraintDefinition.addConstraint
      TableConstraints
ConstraintDefinition.emptyTableConstraints
      [ConstraintDefinition]
constructedConstraints

{- |
  Adds the given table constraints to the field definition. These constraints
  will then be included on any table where the field is used. The constraints
  are passed a function that will take the name of the field definition and
  construct the constraints. This allows the
  'ConstraintDefinition.ConstraintDefinition's to use the correct name of the
  field in the case where 'setFieldName' is used after constraints are added.

  Note: If multiple constraints are added with the same
  'Orville.PostgreSQL.ConstraintMigrationKey', only the last one that is added
  will be part of the 'Orville.PostgreSQL.TableDefinition'. Any previously
  added constraint with the same key is replaced by the new one.

@since 1.0.0.0
-}
addFieldTableConstraints ::
  [FieldName -> ConstraintDefinition.ConstraintDefinition] ->
  FieldDefinition nullability a ->
  FieldDefinition nullability a
addFieldTableConstraints :: forall nullability a.
[FieldName -> ConstraintDefinition]
-> FieldDefinition nullability a -> FieldDefinition nullability a
addFieldTableConstraints [FieldName -> ConstraintDefinition]
constraintDefs FieldDefinition nullability a
fieldDef =
  FieldDefinition nullability a
fieldDef
    { i_fieldTableConstraints :: [FieldName -> ConstraintDefinition]
i_fieldTableConstraints =
        [FieldName -> ConstraintDefinition]
constraintDefs [FieldName -> ConstraintDefinition]
-> [FieldName -> ConstraintDefinition]
-> [FieldName -> ConstraintDefinition]
forall a. Semigroup a => a -> a -> a
<> FieldDefinition nullability a
-> [FieldName -> ConstraintDefinition]
forall nullability a.
FieldDefinition nullability a
-> [FieldName -> ConstraintDefinition]
i_fieldTableConstraints FieldDefinition nullability a
fieldDef
    }

{- |
  Adds a @FOREIGN KEY@ constraint to the 'FieldDefinition' (using
  'addFieldTableConstraints'). This constraint will be included on any table
  that uses the field definition.

@since 1.0.0.0
-}
addForeignKeyConstraint ::
  -- | Identifier of the table referenced by the foreign key.
  TableIdentifier.TableIdentifier ->
  -- | The field name that this field definition references in the foreign table.
  FieldName ->
  FieldDefinition nullability a ->
  FieldDefinition nullability a
addForeignKeyConstraint :: forall nullability a.
TableIdentifier
-> FieldName
-> FieldDefinition nullability a
-> FieldDefinition nullability a
addForeignKeyConstraint TableIdentifier
foreignTableId FieldName
foreignFieldName =
  TableIdentifier
-> FieldName
-> ForeignKeyOptions
-> FieldDefinition nullability a
-> FieldDefinition nullability a
forall nullability a.
TableIdentifier
-> FieldName
-> ForeignKeyOptions
-> FieldDefinition nullability a
-> FieldDefinition nullability a
addForeignKeyConstraintWithOptions
    TableIdentifier
foreignTableId
    FieldName
foreignFieldName
    ForeignKeyOptions
ConstraintDefinition.defaultForeignKeyOptions

{- |
  Adds a @FOREIGN KEY@ constraint to the 'FieldDefinition'. This constraint
  will be included on any table that uses the field definition.

@since 1.0.0.0
-}
addForeignKeyConstraintWithOptions ::
  -- | Identifier of the table referenced by the foreign key.
  TableIdentifier.TableIdentifier ->
  -- | The field name that this field definition references in the foreign table.
  FieldName ->
  ConstraintDefinition.ForeignKeyOptions ->
  FieldDefinition nullability a ->
  FieldDefinition nullability a
addForeignKeyConstraintWithOptions :: forall nullability a.
TableIdentifier
-> FieldName
-> ForeignKeyOptions
-> FieldDefinition nullability a
-> FieldDefinition nullability a
addForeignKeyConstraintWithOptions TableIdentifier
foreignTableId FieldName
foreignFieldName ForeignKeyOptions
options FieldDefinition nullability a
fieldDef =
  let
    mkReference :: FieldName -> ForeignReference
mkReference FieldName
name =
      ConstraintDefinition.ForeignReference
        { localFieldName :: FieldName
ConstraintDefinition.localFieldName = FieldName
name
        , foreignFieldName :: FieldName
ConstraintDefinition.foreignFieldName = FieldName
foreignFieldName
        }

    constraintToAdd :: FieldName -> ConstraintDefinition
constraintToAdd FieldName
name =
      TableIdentifier
-> NonEmpty ForeignReference
-> ForeignKeyOptions
-> ConstraintDefinition
ConstraintDefinition.foreignKeyConstraintWithOptions
        TableIdentifier
foreignTableId
        (FieldName -> ForeignReference
mkReference FieldName
name ForeignReference -> [ForeignReference] -> NonEmpty ForeignReference
forall a. a -> [a] -> NonEmpty a
:| [])
        ForeignKeyOptions
options
  in
    [FieldName -> ConstraintDefinition]
-> FieldDefinition nullability a -> FieldDefinition nullability a
forall nullability a.
[FieldName -> ConstraintDefinition]
-> FieldDefinition nullability a -> FieldDefinition nullability a
addFieldTableConstraints [FieldName -> ConstraintDefinition
constraintToAdd] FieldDefinition nullability a
fieldDef

{- |
  Adds a @UNIQUE@ constraint to the 'FieldDefinition'. This constraint
  will be included on any table that uses the field definition.

@since 1.0.0.0
-}
addUniqueConstraint ::
  FieldDefinition nullability a ->
  FieldDefinition nullability a
addUniqueConstraint :: forall nullability a.
FieldDefinition nullability a -> FieldDefinition nullability a
addUniqueConstraint FieldDefinition nullability a
fieldDef =
  let
    constraintToAdd :: FieldName -> ConstraintDefinition
constraintToAdd FieldName
name =
      NonEmpty FieldName -> ConstraintDefinition
ConstraintDefinition.uniqueConstraint (FieldName
name FieldName -> [FieldName] -> NonEmpty FieldName
forall a. a -> [a] -> NonEmpty a
:| [])
  in
    [FieldName -> ConstraintDefinition]
-> FieldDefinition nullability a -> FieldDefinition nullability a
forall nullability a.
[FieldName -> ConstraintDefinition]
-> FieldDefinition nullability a -> FieldDefinition nullability a
addFieldTableConstraints [FieldName -> ConstraintDefinition
constraintToAdd] FieldDefinition nullability a
fieldDef

{- |
  Marshalls a Haskell value to be stored in the field to its 'SqlValue.SqlValue'
  representation and packages the result as a 'Expr.ValueExpression' so that
  it can be easily used with other @Expr@ functions.

@since 1.0.0.0
-}
fieldValueToExpression :: FieldDefinition nullability a -> a -> Expr.ValueExpression
fieldValueToExpression :: forall nullability a.
FieldDefinition nullability a -> a -> ValueExpression
fieldValueToExpression FieldDefinition nullability a
field =
  SqlValue -> ValueExpression
Expr.valueExpression (SqlValue -> ValueExpression)
-> (a -> SqlValue) -> a -> ValueExpression
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FieldDefinition nullability a -> a -> SqlValue
forall nullability a.
FieldDefinition nullability a -> a -> SqlValue
fieldValueToSqlValue FieldDefinition nullability a
field

{- |
  Marshalls a Haskell value to be stored in the field to its 'SqlValue.SqlValue'
  representation.

@since 1.0.0.0
-}
fieldValueToSqlValue :: FieldDefinition nullability a -> a -> SqlValue.SqlValue
fieldValueToSqlValue :: forall nullability a.
FieldDefinition nullability a -> a -> SqlValue
fieldValueToSqlValue =
  SqlType a -> a -> SqlValue
forall a. SqlType a -> a -> SqlValue
SqlType.sqlTypeToSql (SqlType a -> a -> SqlValue)
-> (FieldDefinition nullability a -> SqlType a)
-> FieldDefinition nullability a
-> a
-> SqlValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FieldDefinition nullability a -> SqlType a
forall nullability a. FieldDefinition nullability a -> SqlType a
fieldType

{- |
  Marshalls a 'SqlValue.SqlValue' from the database into the Haskell value that represents it.
  This may fail, in which case a 'Left' is returned with an error message.

@since 1.0.0.0
-}
fieldValueFromSqlValue :: FieldDefinition nullability a -> SqlValue.SqlValue -> Either String a
fieldValueFromSqlValue :: forall nullability a.
FieldDefinition nullability a -> SqlValue -> Either String a
fieldValueFromSqlValue =
  SqlType a -> SqlValue -> Either String a
forall a. SqlType a -> SqlValue -> Either String a
SqlType.sqlTypeFromSql (SqlType a -> SqlValue -> Either String a)
-> (FieldDefinition nullability a -> SqlType a)
-> FieldDefinition nullability a
-> SqlValue
-> Either String a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FieldDefinition nullability a -> SqlType a
forall nullability a. FieldDefinition nullability a -> SqlType a
fieldType

{- |
  Constructs the 'Expr.ColumnName' for a field for use in SQL expressions
  from the "Orville.PostgreSQL.Expr" module.

@since 1.0.0.0
-}
fieldColumnName :: FieldDefinition nullability a -> Expr.ColumnName
fieldColumnName :: forall nullability a. FieldDefinition nullability a -> ColumnName
fieldColumnName =
  FieldName -> ColumnName
fieldNameToColumnName (FieldName -> ColumnName)
-> (FieldDefinition nullability a -> FieldName)
-> FieldDefinition nullability a
-> ColumnName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FieldDefinition nullability a -> FieldName
forall nullability a. FieldDefinition nullability a -> FieldName
fieldName

{- |
  Constructs the 'Expr.ValueExpression' for a field for use in SQL expressions
  from the "Orville.PostgreSQL.Expr" module.

@since 1.0.0.0
-}
fieldColumnReference :: FieldDefinition nullability a -> Expr.ValueExpression
fieldColumnReference :: forall nullability a.
FieldDefinition nullability a -> ValueExpression
fieldColumnReference =
  ColumnName -> ValueExpression
Expr.columnReference (ColumnName -> ValueExpression)
-> (FieldDefinition nullability a -> ColumnName)
-> FieldDefinition nullability a
-> ValueExpression
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FieldDefinition nullability a -> ColumnName
forall nullability a. FieldDefinition nullability a -> ColumnName
fieldColumnName

{- |
  Constructs the equivalent 'Expr.FieldDefinition' as a SQL expression,
  generally for use in DDL for creating columns in a table.

@since 1.0.0.0
-}
fieldColumnDefinition :: FieldDefinition nullability a -> Expr.ColumnDefinition
fieldColumnDefinition :: forall nullability a.
FieldDefinition nullability a -> ColumnDefinition
fieldColumnDefinition FieldDefinition nullability a
fieldDef =
  ColumnName
-> DataType
-> Maybe ColumnConstraint
-> Maybe ColumnDefault
-> ColumnDefinition
Expr.columnDefinition
    (FieldDefinition nullability a -> ColumnName
forall nullability a. FieldDefinition nullability a -> ColumnName
fieldColumnName FieldDefinition nullability a
fieldDef)
    (SqlType a -> DataType
forall a. SqlType a -> DataType
SqlType.sqlTypeExpr (SqlType a -> DataType) -> SqlType a -> DataType
forall a b. (a -> b) -> a -> b
$ FieldDefinition nullability a -> SqlType a
forall nullability a. FieldDefinition nullability a -> SqlType a
fieldType FieldDefinition nullability a
fieldDef)
    (ColumnConstraint -> Maybe ColumnConstraint
forall a. a -> Maybe a
Just (ColumnConstraint -> Maybe ColumnConstraint)
-> ColumnConstraint -> Maybe ColumnConstraint
forall a b. (a -> b) -> a -> b
$ FieldDefinition nullability a -> ColumnConstraint
forall nullabily a. FieldDefinition nullabily a -> ColumnConstraint
fieldColumnConstraint FieldDefinition nullability a
fieldDef)
    ((DefaultValue a -> ColumnDefault)
-> Maybe (DefaultValue a) -> Maybe ColumnDefault
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (ValueExpression -> ColumnDefault
Expr.columnDefault (ValueExpression -> ColumnDefault)
-> (DefaultValue a -> ValueExpression)
-> DefaultValue a
-> ColumnDefault
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DefaultValue a -> ValueExpression
forall a. DefaultValue a -> ValueExpression
DefaultValue.defaultValueExpression) (Maybe (DefaultValue a) -> Maybe ColumnDefault)
-> Maybe (DefaultValue a) -> Maybe ColumnDefault
forall a b. (a -> b) -> a -> b
$ FieldDefinition nullability a -> Maybe (DefaultValue a)
forall nullability a.
FieldDefinition nullability a -> Maybe (DefaultValue a)
i_fieldDefaultValue FieldDefinition nullability a
fieldDef)

{- |
  INTERNAL - Builds the appropriate ColumnConstraint for a field. Currently
  this only handles nullability, but if we add support for more constraints
  directly on columns it may end up handling those as well.

@since 1.0.0.0
-}
fieldColumnConstraint :: FieldDefinition nullabily a -> Expr.ColumnConstraint
fieldColumnConstraint :: forall nullabily a. FieldDefinition nullabily a -> ColumnConstraint
fieldColumnConstraint FieldDefinition nullabily a
fieldDef =
  case FieldDefinition nullabily a -> FieldNullability a
forall nullability a.
FieldDefinition nullability a -> FieldNullability a
fieldNullability FieldDefinition nullabily a
fieldDef of
    NotNullField FieldDefinition NotNull a
_ ->
      ColumnConstraint
Expr.notNullConstraint
    NullableField FieldDefinition Nullable a
_ ->
      ColumnConstraint
Expr.nullConstraint

{- |

  The type in considered internal because it requires GADTs to make use of
  it meaningfully. The 'FieldNullability' type is used as the public interface
  to surface this information to users outside the module.

  The 'NullabilityGADT' represents whether a field will be marked as @NULL@ or
  'NOT NULL' in the database schema. It is a GADT so that the value
  constructors can be used to record this knowledge in the type system as well.
  This allows functions that work only on 'Nullable' or 'NotNull' fields to
  indicate this in their type signatures as appropriate.

@since 1.0.0.0
-}
data NullabilityGADT nullability where
  NullableGADT :: NullabilityGADT Nullable
  NotNullGADT :: NullabilityGADT NotNull

{- |

  'NotNull' is a valueless type used to track that a 'FieldDefinition'
  represents a field that is marked not-null in the database schema. See the
  'FieldNullability' type for the value-level representation of field nullability.

@since 1.0.0.0
-}
data NotNull

{- |
  'Nullable' is a valueless type used to track that a 'FieldDefinition'
  represents a field that is marked nullable in the database schema. See the
  'FieldNullability' type for the value-level representation of field nullability.

@since 1.0.0.0
-}
data Nullable

{- |
  Builds a 'FieldDefinition' that stores Haskell 'Int32' values as the
  PostgreSQL "INT" type.

@since 1.0.0.0
-}
integerField ::
  -- | Name of the field in the database.
  String ->
  FieldDefinition NotNull Int32
integerField :: String -> FieldDefinition NotNull Int32
integerField = SqlType Int32 -> String -> FieldDefinition NotNull Int32
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType Int32
SqlType.integer

{- |
  Builds a 'FieldDefinition' that stores Haskell 'Int16' values as the
  PostgreSQL "SMALLINT" type.

@since 1.0.0.0
-}
smallIntegerField ::
  -- | Name of the field in the database.
  String ->
  FieldDefinition NotNull Int16
smallIntegerField :: String -> FieldDefinition NotNull Int16
smallIntegerField = SqlType Int16 -> String -> FieldDefinition NotNull Int16
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType Int16
SqlType.smallInteger

{- |
  Builds a 'FieldDefinition' that stores an 'Int32' value as the "SERIAL"
  type. This can be used to create auto-incrementing columns.

@since 1.0.0.0
-}
serialField ::
  -- | Name of the field in the database.
  String ->
  FieldDefinition NotNull Int32
serialField :: String -> FieldDefinition NotNull Int32
serialField = SqlType Int32 -> String -> FieldDefinition NotNull Int32
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType Int32
SqlType.serial

{- |
  Builds a 'FieldDefinition' that stores Haskell 'Int64' values as the
  PostgreSQL "BIGINT" type.

@since 1.0.0.0
-}
bigIntegerField ::
  -- | Name of the field in the database.
  String ->
  FieldDefinition NotNull Int64
bigIntegerField :: String -> FieldDefinition NotNull Int64
bigIntegerField = SqlType Int64 -> String -> FieldDefinition NotNull Int64
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType Int64
SqlType.bigInteger

{- |
  Builds a 'FieldDefinition' that stores an 'Int64' value as the "BIGSERIAL"
  type. This can be used to create auto-incrementing columns.

@since 1.0.0.0
-}
bigSerialField ::
  -- | Name of the field in the database.
  String ->
  FieldDefinition NotNull Int64
bigSerialField :: String -> FieldDefinition NotNull Int64
bigSerialField = SqlType Int64 -> String -> FieldDefinition NotNull Int64
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType Int64
SqlType.bigSerial

{- |
  Builds a 'FieldDefinition' that stores a 'Double' value as the "DOUBLE
  PRECISION" type. Note: PostgreSQL's "DOUBLE PRECISION" type only allows for
  up to 15 digits of precision, so some rounding may occur when values are
  stored in the database.

@since 1.0.0.0
-}
doubleField ::
  -- | Name of the field in the database.
  String ->
  FieldDefinition NotNull Double
doubleField :: String -> FieldDefinition NotNull Double
doubleField = SqlType Double -> String -> FieldDefinition NotNull Double
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType Double
SqlType.double

{- |
  Builds a 'FieldDefinition' that stores Haskell 'Bool' values as the
  PostgreSQL "BOOLEAN" type.

@since 1.0.0.0
-}
booleanField ::
  -- | Name of the field in the database.
  String ->
  FieldDefinition NotNull Bool
booleanField :: String -> FieldDefinition NotNull Bool
booleanField = SqlType Bool -> String -> FieldDefinition NotNull Bool
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType Bool
SqlType.boolean

{- |
  Builds a 'FieldDefinition' that stores Haskell 'T.Text' values as the
  PostgreSQL "TEXT" type. Note that this PostgreSQL has no particular
  limit on the length of text stored.

@since 1.0.0.0
-}
unboundedTextField ::
  -- | Name of the field in the database.
  String ->
  FieldDefinition NotNull T.Text
unboundedTextField :: String -> FieldDefinition NotNull Text
unboundedTextField = SqlType Text -> String -> FieldDefinition NotNull Text
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType Text
SqlType.unboundedText

{- |
  Builds a 'FieldDefinition' that stores Haskell 'T.Text' values as the
  PostgreSQL "VARCHAR" type. Attempting to store a value beyond the length
  specified will cause an error.

@since 1.0.0.0
-}
boundedTextField ::
  -- | Name of the field in the database.
  String ->
  -- | Maximum length of text in the field.
  Int32 ->
  FieldDefinition NotNull T.Text
boundedTextField :: String -> Int32 -> FieldDefinition NotNull Text
boundedTextField String
name Int32
len = SqlType Text -> String -> FieldDefinition NotNull Text
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType (Int32 -> SqlType Text
SqlType.boundedText Int32
len) String
name

{- |
  Builds a 'FieldDefinition' that stores Haskell 'T.Text' values as the
  PostgreSQL "CHAR" type. Attempting to store a value beyond the length
  specified will cause an error. Storing a value that is not the full
  length of the field will result in padding by the database.

@since 1.0.0.0
-}
fixedTextField ::
  -- | Name of the field in the database.
  String ->
  -- | Maximum length of text in the field.
  Int32 ->
  FieldDefinition NotNull T.Text
fixedTextField :: String -> Int32 -> FieldDefinition NotNull Text
fixedTextField String
name Int32
len = SqlType Text -> String -> FieldDefinition NotNull Text
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType (Int32 -> SqlType Text
SqlType.fixedText Int32
len) String
name

{- |
  Builds a @FieldDefinition@ that stores PostgreSQL text search vector values.
  The values are represented as Haskell 'T.Text' values, but are interpreted as
  text search vector values by PostgreSQL when passed to it.

  See https://www.postgresql.org/docs/current/datatype-textsearch.html for
  information about how PostgreSQL creates @tsvector@ values from strings.

@since 1.0.0.0
-}
textSearchVectorField :: String -> FieldDefinition NotNull T.Text
textSearchVectorField :: String -> FieldDefinition NotNull Text
textSearchVectorField = SqlType Text -> String -> FieldDefinition NotNull Text
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType Text
SqlType.textSearchVector

{- |
  Builds a 'FieldDefinition' that stores Haskell 'T.Text' values as the
  PostgreSQL "JSONB" type.

@since 1.0.0.0
-}
jsonbField ::
  String ->
  FieldDefinition NotNull T.Text
jsonbField :: String -> FieldDefinition NotNull Text
jsonbField = SqlType Text -> String -> FieldDefinition NotNull Text
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType Text
SqlType.jsonb

{- |
  Builds a 'FieldDefinition' that stores Haskell 'Time.Day' values as the
  PostgreSQL "DATE" type.

@since 1.0.0.0
-}
dateField ::
  -- | Name of the field in the database.
  String ->
  FieldDefinition NotNull Time.Day
dateField :: String -> FieldDefinition NotNull Day
dateField = SqlType Day -> String -> FieldDefinition NotNull Day
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType Day
SqlType.date

{- |
  Builds a 'FieldDefinition' that stores Haskell 'Time.UTCTime' values as the
  PostgreSQL "TIMESTAMP with time zone" type.

@since 1.0.0.0
-}
utcTimestampField ::
  -- | Name of the field in the database.
  String ->
  FieldDefinition NotNull Time.UTCTime
utcTimestampField :: String -> FieldDefinition NotNull UTCTime
utcTimestampField = SqlType UTCTime -> String -> FieldDefinition NotNull UTCTime
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType UTCTime
SqlType.timestamp

{- |
  Builds a 'FieldDefinition' that stores Haskell 'Time.UTCTime' values as the
  PostgreSQL "TIMESTAMP without time zone" type.

@since 1.0.0.0
-}
localTimestampField ::
  -- | Name of the field in the database.
  String ->
  FieldDefinition NotNull Time.LocalTime
localTimestampField :: String -> FieldDefinition NotNull LocalTime
localTimestampField = SqlType LocalTime -> String -> FieldDefinition NotNull LocalTime
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType LocalTime
SqlType.timestampWithoutZone

{- |
  Builds a 'FieldDefinition' that stores Haskell 'UUID.UUID' values as the
  PostgreSQL "UUID" type.

@since 1.0.0.0
-}
uuidField ::
  -- | Name of the field in the database.
  String ->
  FieldDefinition NotNull UUID.UUID
uuidField :: String -> FieldDefinition NotNull UUID
uuidField = SqlType UUID -> String -> FieldDefinition NotNull UUID
forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType UUID
SqlType.uuid

{- |
  Builds a 'FieldDefinition' that will use the given 'SqlType.SqlType' to
  determine the database representation of the field. If you have created a
  custom 'SqlType.SqlType', you can use this function to construct a helper
  like the other functions in this module for creating 'FieldDefinition's for
  your custom type.

@since 1.0.0.0
-}
fieldOfType ::
  -- | 'SqlType.SqlType' that represents the PostgreSQL data type for the field.
  SqlType.SqlType a ->
  -- | Name of the field in the database.
  String ->
  FieldDefinition NotNull a
fieldOfType :: forall a. SqlType a -> String -> FieldDefinition NotNull a
fieldOfType SqlType a
sqlType String
name =
  FieldDefinition
    { i_fieldName :: FieldName
i_fieldName = String -> FieldName
stringToFieldName String
name
    , i_fieldType :: SqlType a
i_fieldType = SqlType a
sqlType
    , i_fieldNullability :: NullabilityGADT NotNull
i_fieldNullability = NullabilityGADT NotNull
NotNullGADT
    , i_fieldDefaultValue :: Maybe (DefaultValue a)
i_fieldDefaultValue = Maybe (DefaultValue a)
forall a. Maybe a
Nothing
    , i_fieldDescription :: Maybe String
i_fieldDescription = Maybe String
forall a. Maybe a
Nothing
    , i_fieldTableConstraints :: [FieldName -> ConstraintDefinition]
i_fieldTableConstraints = [FieldName -> ConstraintDefinition]
forall a. Monoid a => a
mempty
    }

{- |
  Makes a 'NotNull' field 'Nullable' by wrapping the Haskell type of the field
  in 'Maybe'. The field will be marked as @NULL@ in the database schema and
  the value 'Nothing' will be used to represent @NULL@ values when converting
  to and from SQL.

@since 1.0.0.0
-}
nullableField :: FieldDefinition NotNull a -> FieldDefinition Nullable (Maybe a)
nullableField :: forall a.
FieldDefinition NotNull a -> FieldDefinition Nullable (Maybe a)
nullableField FieldDefinition NotNull a
field =
  let
    nullableType :: SqlType.SqlType a -> SqlType.SqlType (Maybe a)
    nullableType :: forall a. SqlType a -> SqlType (Maybe a)
nullableType SqlType a
sqlType =
      SqlType a
sqlType
        { sqlTypeToSql :: Maybe a -> SqlValue
SqlType.sqlTypeToSql = SqlValue -> (a -> SqlValue) -> Maybe a -> SqlValue
forall b a. b -> (a -> b) -> Maybe a -> b
maybe SqlValue
SqlValue.sqlNull (SqlType a -> a -> SqlValue
forall a. SqlType a -> a -> SqlValue
SqlType.sqlTypeToSql SqlType a
sqlType)
        , sqlTypeFromSql :: SqlValue -> Either String (Maybe a)
SqlType.sqlTypeFromSql =
            \SqlValue
sqlValue ->
              if SqlValue -> Bool
SqlValue.isSqlNull SqlValue
sqlValue
                then Maybe a -> Either String (Maybe a)
forall a b. b -> Either a b
Right Maybe a
forall a. Maybe a
Nothing
                else a -> Maybe a
forall a. a -> Maybe a
Just (a -> Maybe a) -> Either String a -> Either String (Maybe a)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> SqlType a -> SqlValue -> Either String a
forall a. SqlType a -> SqlValue -> Either String a
SqlType.sqlTypeFromSql SqlType a
sqlType SqlValue
sqlValue
        }
  in
    FieldDefinition
      { i_fieldName :: FieldName
i_fieldName = FieldDefinition NotNull a -> FieldName
forall nullability a. FieldDefinition nullability a -> FieldName
fieldName FieldDefinition NotNull a
field
      , i_fieldType :: SqlType (Maybe a)
i_fieldType = SqlType a -> SqlType (Maybe a)
forall a. SqlType a -> SqlType (Maybe a)
nullableType (FieldDefinition NotNull a -> SqlType a
forall nullability a. FieldDefinition nullability a -> SqlType a
fieldType FieldDefinition NotNull a
field)
      , i_fieldNullability :: NullabilityGADT Nullable
i_fieldNullability = NullabilityGADT Nullable
NullableGADT
      , i_fieldDefaultValue :: Maybe (DefaultValue (Maybe a))
i_fieldDefaultValue = (DefaultValue a -> DefaultValue (Maybe a))
-> Maybe (DefaultValue a) -> Maybe (DefaultValue (Maybe a))
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap DefaultValue a -> DefaultValue (Maybe a)
forall a b. DefaultValue a -> DefaultValue b
DefaultValue.coerceDefaultValue (FieldDefinition NotNull a -> Maybe (DefaultValue a)
forall nullability a.
FieldDefinition nullability a -> Maybe (DefaultValue a)
i_fieldDefaultValue FieldDefinition NotNull a
field)
      , i_fieldDescription :: Maybe String
i_fieldDescription = FieldDefinition NotNull a -> Maybe String
forall nullability a. FieldDefinition nullability a -> Maybe String
fieldDescription FieldDefinition NotNull a
field
      , i_fieldTableConstraints :: [FieldName -> ConstraintDefinition]
i_fieldTableConstraints = FieldDefinition NotNull a -> [FieldName -> ConstraintDefinition]
forall nullability a.
FieldDefinition nullability a
-> [FieldName -> ConstraintDefinition]
i_fieldTableConstraints FieldDefinition NotNull a
field
      }

{- |
  Adds a 'Maybe' wrapper to a field that is already nullable. (If your field is
  'NotNull', you wanted 'nullableField' instead of this function). Note that
  fields created using this function have asymmetric encoding and decoding of
  @NULL@ values. Because the provided field is 'Nullable', @NULL@ values decoded
  from the database already have a representation in the @a@ type, so @NULL@
  will be decoded as 'Just <value of type a for NULL>'. This means if you
  insert a 'Nothing' value using the field, it will be read back as 'Just'
  value. This is useful for building high level combinators that might need to
  make fields 'Nullable' but need the value to be decoded in its underlying
  type when reading back (e.g. 'Orville.PostgreSQL.maybeMapper' from
  "Orville.PostgreSQL.Marshall.SqlMarshaller").

@since 1.0.0.0
-}
asymmetricNullableField :: FieldDefinition Nullable a -> FieldDefinition Nullable (Maybe a)
asymmetricNullableField :: forall a.
FieldDefinition Nullable a -> FieldDefinition Nullable (Maybe a)
asymmetricNullableField FieldDefinition Nullable a
field =
  let
    nullableType :: SqlType.SqlType a -> SqlType.SqlType (Maybe a)
    nullableType :: forall a. SqlType a -> SqlType (Maybe a)
nullableType SqlType a
sqlType =
      SqlType a
sqlType
        { sqlTypeToSql :: Maybe a -> SqlValue
SqlType.sqlTypeToSql = SqlValue -> (a -> SqlValue) -> Maybe a -> SqlValue
forall b a. b -> (a -> b) -> Maybe a -> b
maybe SqlValue
SqlValue.sqlNull (SqlType a -> a -> SqlValue
forall a. SqlType a -> a -> SqlValue
SqlType.sqlTypeToSql SqlType a
sqlType)
        , sqlTypeFromSql :: SqlValue -> Either String (Maybe a)
SqlType.sqlTypeFromSql = \SqlValue
sqlValue -> a -> Maybe a
forall a. a -> Maybe a
Just (a -> Maybe a) -> Either String a -> Either String (Maybe a)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> SqlType a -> SqlValue -> Either String a
forall a. SqlType a -> SqlValue -> Either String a
SqlType.sqlTypeFromSql SqlType a
sqlType SqlValue
sqlValue
        }
  in
    FieldDefinition
      { i_fieldName :: FieldName
i_fieldName = FieldDefinition Nullable a -> FieldName
forall nullability a. FieldDefinition nullability a -> FieldName
fieldName FieldDefinition Nullable a
field
      , i_fieldType :: SqlType (Maybe a)
i_fieldType = SqlType a -> SqlType (Maybe a)
forall a. SqlType a -> SqlType (Maybe a)
nullableType (FieldDefinition Nullable a -> SqlType a
forall nullability a. FieldDefinition nullability a -> SqlType a
fieldType FieldDefinition Nullable a
field)
      , i_fieldNullability :: NullabilityGADT Nullable
i_fieldNullability = NullabilityGADT Nullable
NullableGADT
      , i_fieldDefaultValue :: Maybe (DefaultValue (Maybe a))
i_fieldDefaultValue = (DefaultValue a -> DefaultValue (Maybe a))
-> Maybe (DefaultValue a) -> Maybe (DefaultValue (Maybe a))
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap DefaultValue a -> DefaultValue (Maybe a)
forall a b. DefaultValue a -> DefaultValue b
DefaultValue.coerceDefaultValue (FieldDefinition Nullable a -> Maybe (DefaultValue a)
forall nullability a.
FieldDefinition nullability a -> Maybe (DefaultValue a)
i_fieldDefaultValue FieldDefinition Nullable a
field)
      , i_fieldDescription :: Maybe String
i_fieldDescription = FieldDefinition Nullable a -> Maybe String
forall nullability a. FieldDefinition nullability a -> Maybe String
fieldDescription FieldDefinition Nullable a
field
      , i_fieldTableConstraints :: [FieldName -> ConstraintDefinition]
i_fieldTableConstraints = FieldDefinition Nullable a -> [FieldName -> ConstraintDefinition]
forall nullability a.
FieldDefinition nullability a
-> [FieldName -> ConstraintDefinition]
i_fieldTableConstraints FieldDefinition Nullable a
field
      }

{- |
  Applies a 'SqlType.SqlType' conversion to a 'FieldDefinition'. You can
  use this function to create 'FieldDefinition's based on the primitive ones
  provided, but with more specific Haskell types.

  See 'SqlType.convertSqlType' and 'SqlType.tryConvertSqlType' for functions
  to create the conversion needed as the first argument to 'convertField'.

@since 1.0.0.0
-}
convertField ::
  (SqlType.SqlType a -> SqlType.SqlType b) ->
  FieldDefinition nullability a ->
  FieldDefinition nullability b
convertField :: forall a b nullability.
(SqlType a -> SqlType b)
-> FieldDefinition nullability a -> FieldDefinition nullability b
convertField SqlType a -> SqlType b
conversion FieldDefinition nullability a
fieldDef =
  FieldDefinition nullability a
fieldDef
    { i_fieldType :: SqlType b
i_fieldType = SqlType a -> SqlType b
conversion (FieldDefinition nullability a -> SqlType a
forall nullability a. FieldDefinition nullability a -> SqlType a
i_fieldType FieldDefinition nullability a
fieldDef)
    , i_fieldDefaultValue :: Maybe (DefaultValue b)
i_fieldDefaultValue = (DefaultValue a -> DefaultValue b)
-> Maybe (DefaultValue a) -> Maybe (DefaultValue b)
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap DefaultValue a -> DefaultValue b
forall a b. DefaultValue a -> DefaultValue b
DefaultValue.coerceDefaultValue (FieldDefinition nullability a -> Maybe (DefaultValue a)
forall nullability a.
FieldDefinition nullability a -> Maybe (DefaultValue a)
i_fieldDefaultValue FieldDefinition nullability a
fieldDef)
    }

{- |
  A specialization of 'convertField' that can be used with types that implement
  'Coerce.Coercible'. This is particularly useful for newtype wrappers around
  primitive types.

@since 1.0.0.0
-}
coerceField ::
  (Coerce.Coercible a b, Coerce.Coercible b a) =>
  FieldDefinition nullability a ->
  FieldDefinition nullability b
coerceField :: forall a b nullability.
(Coercible a b, Coercible b a) =>
FieldDefinition nullability a -> FieldDefinition nullability b
coerceField =
  (SqlType a -> SqlType b)
-> FieldDefinition nullability a -> FieldDefinition nullability b
forall a b nullability.
(SqlType a -> SqlType b)
-> FieldDefinition nullability a -> FieldDefinition nullability b
convertField
    ((b -> a) -> (a -> b) -> SqlType a -> SqlType b
forall b a. (b -> a) -> (a -> b) -> SqlType a -> SqlType b
SqlType.convertSqlType b -> a
forall a b. Coercible a b => a -> b
Coerce.coerce a -> b
forall a b. Coercible a b => a -> b
Coerce.coerce)

{- |
  Sets a default value for the field. The default value will be added as part
  of the column definition in the database. Because the default value is
  ultimately provided by the database, this can be used to add a not-null column
  safely to an existing table as long as a reasonable default value is
  available to use.

@since 1.0.0.0
-}
setDefaultValue ::
  DefaultValue.DefaultValue a ->
  FieldDefinition nullability a ->
  FieldDefinition nullability a
setDefaultValue :: forall a nullability.
DefaultValue a
-> FieldDefinition nullability a -> FieldDefinition nullability a
setDefaultValue DefaultValue a
defaultValue FieldDefinition nullability a
fieldDef =
  FieldDefinition nullability a
fieldDef
    { i_fieldDefaultValue :: Maybe (DefaultValue a)
i_fieldDefaultValue = DefaultValue a -> Maybe (DefaultValue a)
forall a. a -> Maybe a
Just DefaultValue a
defaultValue
    }

{- |
  Removes any default value that may have been set on a field via
  @setDefaultValue@.

@since 1.0.0.0
-}
removeDefaultValue ::
  FieldDefinition nullability a ->
  FieldDefinition nullability a
removeDefaultValue :: forall nullability a.
FieldDefinition nullability a -> FieldDefinition nullability a
removeDefaultValue FieldDefinition nullability a
fieldDef =
  FieldDefinition nullability a
fieldDef
    { i_fieldDefaultValue :: Maybe (DefaultValue a)
i_fieldDefaultValue = Maybe (DefaultValue a)
forall a. Maybe a
Nothing
    }

{- |
  Adds a prefix, followed by an underscore, to a field's name.

@since 1.0.0.0
-}
prefixField ::
  String ->
  FieldDefinition nullability a ->
  FieldDefinition nullability a
prefixField :: forall nullability a.
String
-> FieldDefinition nullability a -> FieldDefinition nullability a
prefixField String
prefix FieldDefinition nullability a
fieldDef =
  FieldDefinition nullability a
fieldDef
    { i_fieldName :: FieldName
i_fieldName = ByteString -> FieldName
byteStringToFieldName (String -> ByteString
B8.pack String
prefix ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
"_" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> FieldName -> ByteString
fieldNameToByteString (FieldDefinition nullability a -> FieldName
forall nullability a. FieldDefinition nullability a -> FieldName
fieldName FieldDefinition nullability a
fieldDef))
    }

{- |
  Constructs a 'Expr.SetClause' that will set the column named in the
  field definition to the given value. The value is converted to a SQL
  value using 'fieldValueToSqlValue'.

@since 1.0.0.0
-}
setField :: FieldDefinition nullability a -> a -> Expr.SetClause
setField :: forall nullability a.
FieldDefinition nullability a -> a -> SetClause
setField FieldDefinition nullability a
fieldDef a
value =
  ColumnName -> SqlValue -> SetClause
Expr.setColumn
    (FieldDefinition nullability a -> ColumnName
forall nullability a. FieldDefinition nullability a -> ColumnName
fieldColumnName FieldDefinition nullability a
fieldDef)
    (FieldDefinition nullability a -> a -> SqlValue
forall nullability a.
FieldDefinition nullability a -> a -> SqlValue
fieldValueToSqlValue FieldDefinition nullability a
fieldDef a
value)

{- |
  Operator alias for 'setField'.

@since 1.0.0.0
-}
(.:=) :: FieldDefinition nullability a -> a -> Expr.SetClause
.:= :: forall nullability a.
FieldDefinition nullability a -> a -> SetClause
(.:=) = FieldDefinition nullability a -> a -> SetClause
forall nullability a.
FieldDefinition nullability a -> a -> SetClause
setField

{- |
  Checks that the value in a field equals a particular value.

@since 1.0.0.0
-}
fieldEquals :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
fieldEquals :: forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
fieldEquals =
  (ValueExpression -> ValueExpression -> BooleanExpr)
-> FieldDefinition nullability a -> a -> BooleanExpr
forall nullability a.
(ValueExpression -> ValueExpression -> BooleanExpr)
-> FieldDefinition nullability a -> a -> BooleanExpr
whereColumnComparison ValueExpression -> ValueExpression -> BooleanExpr
Expr.equals

{- |
  Operator alias for 'fieldEquals'.

@since 1.0.0.0
-}
(.==) :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
.== :: forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
(.==) = FieldDefinition nullability a -> a -> BooleanExpr
forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
fieldEquals

infixl 9 .==

{- |
  Checks that the value in a field does not equal a particular value.

@since 1.0.0.0
-}
fieldNotEquals :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
fieldNotEquals :: forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
fieldNotEquals =
  (ValueExpression -> ValueExpression -> BooleanExpr)
-> FieldDefinition nullability a -> a -> BooleanExpr
forall nullability a.
(ValueExpression -> ValueExpression -> BooleanExpr)
-> FieldDefinition nullability a -> a -> BooleanExpr
whereColumnComparison ValueExpression -> ValueExpression -> BooleanExpr
Expr.notEquals

{- |
  Operator alias for 'fieldNotEquals'.

@since 1.0.0.0
-}
(./=) :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
./= :: forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
(./=) = FieldDefinition nullability a -> a -> BooleanExpr
forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
fieldNotEquals

infixl 9 ./=

{- |
  Checks that the value in a field is greater than a particular value.

@since 1.0.0.0
-}
fieldGreaterThan :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
fieldGreaterThan :: forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
fieldGreaterThan =
  (ValueExpression -> ValueExpression -> BooleanExpr)
-> FieldDefinition nullability a -> a -> BooleanExpr
forall nullability a.
(ValueExpression -> ValueExpression -> BooleanExpr)
-> FieldDefinition nullability a -> a -> BooleanExpr
whereColumnComparison ValueExpression -> ValueExpression -> BooleanExpr
Expr.greaterThan

{- |
  Operator alias for 'fieldGreaterThan'.

@since 1.0.0.0
-}
(.>) :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
.> :: forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
(.>) = FieldDefinition nullability a -> a -> BooleanExpr
forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
fieldGreaterThan

infixl 9 .>

{- |
  Checks that the value in a field is less than a particular value.

@since 1.0.0.0
-}
fieldLessThan :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
fieldLessThan :: forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
fieldLessThan =
  (ValueExpression -> ValueExpression -> BooleanExpr)
-> FieldDefinition nullability a -> a -> BooleanExpr
forall nullability a.
(ValueExpression -> ValueExpression -> BooleanExpr)
-> FieldDefinition nullability a -> a -> BooleanExpr
whereColumnComparison ValueExpression -> ValueExpression -> BooleanExpr
Expr.lessThan

{- |
  Operator alias for 'fieldLessThan'.

@since 1.0.0.0
-}
(.<) :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
.< :: forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
(.<) = FieldDefinition nullability a -> a -> BooleanExpr
forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
fieldLessThan

infixl 9 .<

{- |
  Checks that the value in a field is greater than or equal to a particular value.

@since 1.0.0.0
-}
fieldGreaterThanOrEqualTo :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
fieldGreaterThanOrEqualTo :: forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
fieldGreaterThanOrEqualTo =
  (ValueExpression -> ValueExpression -> BooleanExpr)
-> FieldDefinition nullability a -> a -> BooleanExpr
forall nullability a.
(ValueExpression -> ValueExpression -> BooleanExpr)
-> FieldDefinition nullability a -> a -> BooleanExpr
whereColumnComparison ValueExpression -> ValueExpression -> BooleanExpr
Expr.greaterThanOrEqualTo

{- |
  Operator alias for 'fieldGreaterThanOrEqualTo'.

@since 1.0.0.0
-}
(.>=) :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
.>= :: forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
(.>=) = FieldDefinition nullability a -> a -> BooleanExpr
forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
fieldGreaterThanOrEqualTo

infixl 9 .>=

{- |
  Checks that the value in a field is less than or equal to a particular value.

@since 1.0.0.0
-}
fieldLessThanOrEqualTo :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
fieldLessThanOrEqualTo :: forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
fieldLessThanOrEqualTo =
  (ValueExpression -> ValueExpression -> BooleanExpr)
-> FieldDefinition nullability a -> a -> BooleanExpr
forall nullability a.
(ValueExpression -> ValueExpression -> BooleanExpr)
-> FieldDefinition nullability a -> a -> BooleanExpr
whereColumnComparison ValueExpression -> ValueExpression -> BooleanExpr
Expr.lessThanOrEqualTo

{- |
  Operator alias for 'fieldLessThanOrEqualTo'.

@since 1.0.0.0
-}
(.<=) :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
.<= :: forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
(.<=) = FieldDefinition nullability a -> a -> BooleanExpr
forall nullability a.
FieldDefinition nullability a -> a -> BooleanExpr
fieldLessThanOrEqualTo

infixl 9 .<=

{- |
  Checks that the value in a field matches a like pattern.

@since 1.0.0.0
-}
fieldLike :: FieldDefinition nullability a -> T.Text -> Expr.BooleanExpr
fieldLike :: forall nullability a.
FieldDefinition nullability a -> Text -> BooleanExpr
fieldLike FieldDefinition nullability a
fieldDef Text
likePattern =
  ValueExpression -> ValueExpression -> BooleanExpr
Expr.like
    (FieldDefinition nullability a -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> ValueExpression
fieldColumnReference FieldDefinition nullability a
fieldDef)
    (SqlValue -> ValueExpression
Expr.valueExpression (Text -> SqlValue
SqlValue.fromText Text
likePattern))

{- |
  Checks that the value in a field matches a like pattern case insensitively.

@since 1.0.0.0
-}
fieldLikeInsensitive :: FieldDefinition nullability a -> T.Text -> Expr.BooleanExpr
fieldLikeInsensitive :: forall nullability a.
FieldDefinition nullability a -> Text -> BooleanExpr
fieldLikeInsensitive FieldDefinition nullability a
fieldDef Text
likePattern =
  ValueExpression -> ValueExpression -> BooleanExpr
Expr.likeInsensitive
    (FieldDefinition nullability a -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> ValueExpression
fieldColumnReference FieldDefinition nullability a
fieldDef)
    (SqlValue -> ValueExpression
Expr.valueExpression (Text -> SqlValue
SqlValue.fromText Text
likePattern))

{- |
  Checks that the value in a field is null.

@since 1.0.0.0
-}
fieldIsNull :: FieldDefinition Nullable a -> Expr.BooleanExpr
fieldIsNull :: forall a. FieldDefinition Nullable a -> BooleanExpr
fieldIsNull =
  ValueExpression -> BooleanExpr
Expr.isNull (ValueExpression -> BooleanExpr)
-> (FieldDefinition Nullable a -> ValueExpression)
-> FieldDefinition Nullable a
-> BooleanExpr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FieldDefinition Nullable a -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> ValueExpression
fieldColumnReference

{- |
  Checks that the value in a field is not null.

@since 1.0.0.0
-}
fieldIsNotNull :: FieldDefinition Nullable a -> Expr.BooleanExpr
fieldIsNotNull :: forall a. FieldDefinition Nullable a -> BooleanExpr
fieldIsNotNull =
  ValueExpression -> BooleanExpr
Expr.isNotNull (ValueExpression -> BooleanExpr)
-> (FieldDefinition Nullable a -> ValueExpression)
-> FieldDefinition Nullable a
-> BooleanExpr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FieldDefinition Nullable a -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> ValueExpression
fieldColumnReference

{- |
  Checks that a field matches a list of values.

@since 1.0.0.0
-}
fieldIn :: FieldDefinition nullability a -> NonEmpty a -> Expr.BooleanExpr
fieldIn :: forall nullability a.
FieldDefinition nullability a -> NonEmpty a -> BooleanExpr
fieldIn FieldDefinition nullability a
fieldDef NonEmpty a
values =
  ValueExpression -> NonEmpty ValueExpression -> BooleanExpr
Expr.valueIn
    (FieldDefinition nullability a -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> ValueExpression
fieldColumnReference FieldDefinition nullability a
fieldDef)
    ((a -> ValueExpression) -> NonEmpty a -> NonEmpty ValueExpression
forall a b. (a -> b) -> NonEmpty a -> NonEmpty b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (FieldDefinition nullability a -> a -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> a -> ValueExpression
fieldValueToExpression FieldDefinition nullability a
fieldDef) NonEmpty a
values)

{- |
  Operator alias for 'fieldIn'.

@since 1.0.0.0
-}
(.<-) :: FieldDefinition nullability a -> NonEmpty a -> Expr.BooleanExpr
.<- :: forall nullability a.
FieldDefinition nullability a -> NonEmpty a -> BooleanExpr
(.<-) = FieldDefinition nullability a -> NonEmpty a -> BooleanExpr
forall nullability a.
FieldDefinition nullability a -> NonEmpty a -> BooleanExpr
fieldIn

infixl 9 .<-

{- |
  Checks that a field does not match a list of values.

@since 1.0.0.0
-}
fieldNotIn :: FieldDefinition nullability a -> NonEmpty a -> Expr.BooleanExpr
fieldNotIn :: forall nullability a.
FieldDefinition nullability a -> NonEmpty a -> BooleanExpr
fieldNotIn FieldDefinition nullability a
fieldDef NonEmpty a
values =
  ValueExpression -> NonEmpty ValueExpression -> BooleanExpr
Expr.valueNotIn
    (FieldDefinition nullability a -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> ValueExpression
fieldColumnReference FieldDefinition nullability a
fieldDef)
    ((a -> ValueExpression) -> NonEmpty a -> NonEmpty ValueExpression
forall a b. (a -> b) -> NonEmpty a -> NonEmpty b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (FieldDefinition nullability a -> a -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> a -> ValueExpression
fieldValueToExpression FieldDefinition nullability a
fieldDef) NonEmpty a
values)

{- |
  Operator alias for 'fieldNotIn'.

@since 1.0.0.0
-}
(.</-) :: FieldDefinition nullability a -> NonEmpty a -> Expr.BooleanExpr
.</- :: forall nullability a.
FieldDefinition nullability a -> NonEmpty a -> BooleanExpr
(.</-) = FieldDefinition nullability a -> NonEmpty a -> BooleanExpr
forall nullability a.
FieldDefinition nullability a -> NonEmpty a -> BooleanExpr
fieldNotIn

infixl 9 .</-

{- |
  Checks that a tuple of two fields is in the list of specified tuples.

@since 1.0.0.0
-}
fieldTupleIn ::
  FieldDefinition nullabilityA a ->
  FieldDefinition nullabilityB b ->
  NonEmpty (a, b) ->
  Expr.BooleanExpr
fieldTupleIn :: forall nullabilityA a nullabilityB b.
FieldDefinition nullabilityA a
-> FieldDefinition nullabilityB b -> NonEmpty (a, b) -> BooleanExpr
fieldTupleIn FieldDefinition nullabilityA a
fieldDefA FieldDefinition nullabilityB b
fieldDefB NonEmpty (a, b)
values =
  NonEmpty ValueExpression
-> NonEmpty (NonEmpty ValueExpression) -> BooleanExpr
Expr.tupleIn
    (FieldDefinition nullabilityA a -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> ValueExpression
fieldColumnReference FieldDefinition nullabilityA a
fieldDefA ValueExpression -> [ValueExpression] -> NonEmpty ValueExpression
forall a. a -> [a] -> NonEmpty a
:| [FieldDefinition nullabilityB b -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> ValueExpression
fieldColumnReference FieldDefinition nullabilityB b
fieldDefB])
    (((a, b) -> NonEmpty ValueExpression)
-> NonEmpty (a, b) -> NonEmpty (NonEmpty ValueExpression)
forall a b. (a -> b) -> NonEmpty a -> NonEmpty b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (FieldDefinition nullabilityA a
-> FieldDefinition nullabilityB b
-> (a, b)
-> NonEmpty ValueExpression
forall nullabilityA a nullabilityB b.
FieldDefinition nullabilityA a
-> FieldDefinition nullabilityB b
-> (a, b)
-> NonEmpty ValueExpression
toSqlValueTuple FieldDefinition nullabilityA a
fieldDefA FieldDefinition nullabilityB b
fieldDefB) NonEmpty (a, b)
values)

{- |
  Checks that a tuple of two fields is not in the list of specified tuples.

@since 1.0.0.0
-}
fieldTupleNotIn ::
  FieldDefinition nullabilityA a ->
  FieldDefinition nullabilityB b ->
  NonEmpty (a, b) ->
  Expr.BooleanExpr
fieldTupleNotIn :: forall nullabilityA a nullabilityB b.
FieldDefinition nullabilityA a
-> FieldDefinition nullabilityB b -> NonEmpty (a, b) -> BooleanExpr
fieldTupleNotIn FieldDefinition nullabilityA a
fieldDefA FieldDefinition nullabilityB b
fieldDefB NonEmpty (a, b)
values =
  NonEmpty ValueExpression
-> NonEmpty (NonEmpty ValueExpression) -> BooleanExpr
Expr.tupleNotIn
    (FieldDefinition nullabilityA a -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> ValueExpression
fieldColumnReference FieldDefinition nullabilityA a
fieldDefA ValueExpression -> [ValueExpression] -> NonEmpty ValueExpression
forall a. a -> [a] -> NonEmpty a
:| [FieldDefinition nullabilityB b -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> ValueExpression
fieldColumnReference FieldDefinition nullabilityB b
fieldDefB])
    (((a, b) -> NonEmpty ValueExpression)
-> NonEmpty (a, b) -> NonEmpty (NonEmpty ValueExpression)
forall a b. (a -> b) -> NonEmpty a -> NonEmpty b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (FieldDefinition nullabilityA a
-> FieldDefinition nullabilityB b
-> (a, b)
-> NonEmpty ValueExpression
forall nullabilityA a nullabilityB b.
FieldDefinition nullabilityA a
-> FieldDefinition nullabilityB b
-> (a, b)
-> NonEmpty ValueExpression
toSqlValueTuple FieldDefinition nullabilityA a
fieldDefA FieldDefinition nullabilityB b
fieldDefB) NonEmpty (a, b)
values)

{- |
  Constructs a SqlValue "tuple" (i.e. NonEmpty list) for two fields.

@since 1.0.0.0
-}
toSqlValueTuple ::
  FieldDefinition nullabilityA a ->
  FieldDefinition nullabilityB b ->
  (a, b) ->
  NonEmpty Expr.ValueExpression
toSqlValueTuple :: forall nullabilityA a nullabilityB b.
FieldDefinition nullabilityA a
-> FieldDefinition nullabilityB b
-> (a, b)
-> NonEmpty ValueExpression
toSqlValueTuple FieldDefinition nullabilityA a
fieldDefA FieldDefinition nullabilityB b
fieldDefB (a
a, b
b) =
  FieldDefinition nullabilityA a -> a -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> a -> ValueExpression
fieldValueToExpression FieldDefinition nullabilityA a
fieldDefA a
a
    ValueExpression -> [ValueExpression] -> NonEmpty ValueExpression
forall a. a -> [a] -> NonEmpty a
:| [FieldDefinition nullabilityB b -> b -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> a -> ValueExpression
fieldValueToExpression FieldDefinition nullabilityB b
fieldDefB b
b]

{- |
  Constructs a field-based 'Expr.BooleanExpr' using a function that
  builds a 'Expr.BooleanExpr'.

@since 1.0.0.0
-}
whereColumnComparison ::
  (Expr.ValueExpression -> Expr.ValueExpression -> Expr.BooleanExpr) ->
  (FieldDefinition nullability a -> a -> Expr.BooleanExpr)
whereColumnComparison :: forall nullability a.
(ValueExpression -> ValueExpression -> BooleanExpr)
-> FieldDefinition nullability a -> a -> BooleanExpr
whereColumnComparison ValueExpression -> ValueExpression -> BooleanExpr
columnComparison FieldDefinition nullability a
fieldDef a
a =
  ValueExpression -> ValueExpression -> BooleanExpr
columnComparison
    (FieldDefinition nullability a -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> ValueExpression
fieldColumnReference FieldDefinition nullability a
fieldDef)
    (FieldDefinition nullability a -> a -> ValueExpression
forall nullability a.
FieldDefinition nullability a -> a -> ValueExpression
fieldValueToExpression FieldDefinition nullability a
fieldDef a
a)

{-- |
  Orders a query by the column name for the given field.
--}
orderByField ::
  FieldDefinition nullability value ->
  Expr.OrderByDirection ->
  Expr.OrderByExpr
orderByField :: forall nullability value.
FieldDefinition nullability value
-> OrderByDirection -> OrderByExpr
orderByField =
  ColumnName -> OrderByDirection -> OrderByExpr
Expr.orderByColumnName (ColumnName -> OrderByDirection -> OrderByExpr)
-> (FieldDefinition nullability value -> ColumnName)
-> FieldDefinition nullability value
-> OrderByDirection
-> OrderByExpr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FieldDefinition nullability value -> ColumnName
forall nullability a. FieldDefinition nullability a -> ColumnName
fieldColumnName