orville-postgresql-1.1.0.0: src/Orville/PostgreSQL/Marshall/FieldDefinition.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{- |
Copyright : Flipstone Technology Partners 2023-2025
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
, (./=)
, fieldIsDistinctFrom
, fieldIsNotDistinctFrom
, 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
, QualifiedFieldDefinition
, qualifyField
, qualifiedFieldColumnName
, FieldQualifier (qualifyColumnName)
, FieldIdentityGeneration (GeneratedAlways, GeneratedByDefault)
, markAsIdentity
, unmarkIdentity
, fieldIdentityGeneration
)
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.AliasName as AliasName
import qualified Orville.PostgreSQL.Marshall.DefaultValue as DefaultValue
import Orville.PostgreSQL.Marshall.SqlComparable (SqlComparable (referenceValueExpression, toComparableSqlValue))
import qualified Orville.PostgreSQL.Marshall.SqlComparable as SqlComparable
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
{ i_fieldName :: FieldName
, i_fieldType :: SqlType.SqlType a
, i_fieldNullability :: NullabilityGADT nullability
, i_fieldDefaultValue :: Maybe (DefaultValue.DefaultValue a)
, i_fieldDescription :: Maybe String
, i_fieldTableConstraints :: [FieldName -> ConstraintDefinition.ConstraintDefinition]
, i_fieldIdentity :: IdentityGADT nullability
}
{- | Constructs the 'Expr.ValueExpression' for a field for use in SQL expressions
from the "Orville.PostgreSQL.Expr" module.
@since 1.1.0.0
-}
instance SqlComparable.SqlComparable (FieldDefinition nullability a) a where
toComparableSqlValue = fieldValueToSqlValue
referenceValueExpression = fieldColumnReference
{- | The name used in database queries to reference the field.
@since 1.0.0.0
-}
fieldName :: FieldDefinition nullability a -> FieldName
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 newName fieldDef =
fieldDef
{ i_fieldName = newName
}
{- | Returns the description that was passed to 'setFieldDescription', if any.
@since 1.0.0.0
-}
fieldDescription :: FieldDefinition nullability a -> Maybe String
fieldDescription = i_fieldDescription
{- | Sets the description for the field. This description will be used to add
a comment to the column, and 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 description fieldDef =
fieldDef
{ i_fieldDescription = Just 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 = 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 = 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 field =
case i_fieldNullability field of
NullableGADT -> NullableField field
NotNullGADT -> NotNullField field
{- | Indicates whether a field is not nullable.
@since 1.0.0.0
-}
fieldIsNotNullable :: FieldDefinition nullability a -> Bool
fieldIsNotNullable field =
case i_fieldNullability field of
NullableGADT -> False
NotNullGADT -> 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 fieldDef =
let
name =
fieldName fieldDef
constructedConstraints =
fmap ($ name) (i_fieldTableConstraints fieldDef)
in
foldr
ConstraintDefinition.addConstraint
ConstraintDefinition.emptyTableConstraints
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 constraintDefs fieldDef =
fieldDef
{ i_fieldTableConstraints =
constraintDefs <> i_fieldTableConstraints 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 foreignTableId foreignFieldName =
addForeignKeyConstraintWithOptions
foreignTableId
foreignFieldName
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 foreignTableId foreignFieldName options fieldDef =
let
mkReference name =
ConstraintDefinition.ForeignReference
{ ConstraintDefinition.localFieldName = name
, ConstraintDefinition.foreignFieldName = foreignFieldName
}
constraintToAdd name =
ConstraintDefinition.foreignKeyConstraintWithOptions
foreignTableId
(mkReference name :| [])
options
in
addFieldTableConstraints [constraintToAdd] 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 fieldDef =
let
constraintToAdd name =
ConstraintDefinition.uniqueConstraint (name :| [])
in
addFieldTableConstraints [constraintToAdd] 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 field =
Expr.valueExpression . fieldValueToSqlValue 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 = SqlType.sqlTypeToSql . 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 =
SqlType.sqlTypeFromSql . 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 =
fieldNameToColumnName . fieldName
{- | Constructs the 'Expr.ValueExpression' for use in SQL expressions from the
"Orville.PostgreSQL.Expr" module.
@since 1.0.0.0
-}
fieldColumnReference :: FieldDefinition nullability a -> Expr.ValueExpression
fieldColumnReference =
Expr.columnReference
. Expr.unqualified
. fieldNameToColumnName
. fieldName
{- | 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 fieldDef =
Expr.columnDefinition
(fieldNameToColumnName $ fieldName fieldDef)
(SqlType.sqlTypeExpr $ fieldType fieldDef)
(fieldColumnConstraints fieldDef)
(fmap (Expr.columnDefault . DefaultValue.defaultValueExpression) $ i_fieldDefaultValue fieldDef)
{- | INTERNAL - Builds the appropriate [ColumnConstraint] for a field.
@since 1.0.0.0
-}
fieldColumnConstraints :: FieldDefinition nullabily a -> [Expr.ColumnConstraint]
fieldColumnConstraints fieldDef =
case fieldNullability fieldDef of
NotNullField nnf ->
case i_fieldIdentity nnf of
IsIdentityGADT GeneratedAlways ->
[Expr.notNullConstraint, Expr.identityColumnConstraint Expr.alwaysColumnIdentityGeneration]
IsIdentityGADT GeneratedByDefault ->
[Expr.notNullConstraint, Expr.identityColumnConstraint Expr.byDefaultColumnIdentityGeneration]
AllowedIdentityButNotSetGADT ->
pure Expr.notNullConstraint
NullableField _ ->
pure 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 = fieldOfType 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 = fieldOfType 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 = fieldOfType 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 = fieldOfType 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 = fieldOfType 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 = fieldOfType 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 = fieldOfType 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 = fieldOfType 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 name len = fieldOfType (SqlType.boundedText len) 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 name len = fieldOfType (SqlType.fixedText len) 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 = fieldOfType 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 = fieldOfType SqlType.jsonb
{- | Builds a 'FieldDefinition' that stores Haskell 'Time.Day' values as the
PostgreSQL "DATE" type.
This field cannot represent the full range of 'Time.Day' values. PostgreSQL supports years
from -4731 to 5874897 inclusive for this field, and sending a 'Time.Day' with a year outside
of this range to the database will result in a PostgreSQL exception.
@since 1.0.0.0
-}
dateField ::
-- | Name of the field in the database.
String ->
FieldDefinition NotNull Time.Day
dateField = fieldOfType SqlType.date
{- | Builds a 'FieldDefinition' that stores Haskell 'Time.UTCTime' values as the
PostgreSQL "TIMESTAMP with time zone" type.
This field cannot represent the full range of 'Time.UTCTime' values. PostgreSQL supports years
from -4731 to 294276 inclusive for this field, and sending a 'Time.UTCTime' with a year outside
of this range to the database will result in a PostgreSQL exception.
@since 1.0.0.0
-}
utcTimestampField ::
-- | Name of the field in the database.
String ->
FieldDefinition NotNull Time.UTCTime
utcTimestampField = fieldOfType SqlType.timestamp
{- | Builds a 'FieldDefinition' that stores Haskell 'Time.UTCTime' values as the
PostgreSQL "TIMESTAMP without time zone" type.
This field cannot represent the full range of 'Time.LocalTime' values. PostgreSQL supports years
from -4731 to 294276 inclusive for this field, and sending a 'Time.LocalTime' with a year outside
of this range to the database will result in a PostgreSQL exception.
@since 1.0.0.0
-}
localTimestampField ::
-- | Name of the field in the database.
String ->
FieldDefinition NotNull Time.LocalTime
localTimestampField = fieldOfType 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 = fieldOfType 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 sqlType name =
FieldDefinition
{ i_fieldName = stringToFieldName name
, i_fieldType = sqlType
, i_fieldNullability = NotNullGADT
, i_fieldDefaultValue = Nothing
, i_fieldDescription = Nothing
, i_fieldTableConstraints = mempty
, i_fieldIdentity = AllowedIdentityButNotSetGADT
}
{- | 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. If the field was previously an indentity
column, that will be removed.
@since 1.0.0.0
-}
nullableField :: FieldDefinition NotNull a -> FieldDefinition Nullable (Maybe a)
nullableField field =
let
nullableType :: SqlType.SqlType a -> SqlType.SqlType (Maybe a)
nullableType sqlType =
sqlType
{ SqlType.sqlTypeToSql = maybe SqlValue.sqlNull (SqlType.sqlTypeToSql sqlType)
, SqlType.sqlTypeFromSql =
\sqlValue ->
if SqlValue.isSqlNull sqlValue
then Right Nothing
else Just <$> SqlType.sqlTypeFromSql sqlType sqlValue
}
in
FieldDefinition
{ i_fieldName = fieldName field
, i_fieldType = nullableType (fieldType field)
, i_fieldNullability = NullableGADT
, i_fieldDefaultValue = fmap DefaultValue.coerceDefaultValue (i_fieldDefaultValue field)
, i_fieldDescription = fieldDescription field
, i_fieldTableConstraints = i_fieldTableConstraints field
, i_fieldIdentity = NotIdentityGADT
}
{- | 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 field =
let
nullableType :: SqlType.SqlType a -> SqlType.SqlType (Maybe a)
nullableType sqlType =
sqlType
{ SqlType.sqlTypeToSql = maybe SqlValue.sqlNull (SqlType.sqlTypeToSql sqlType)
, SqlType.sqlTypeFromSql = fmap Just . SqlType.sqlTypeFromSql sqlType
}
in
FieldDefinition
{ i_fieldName = fieldName field
, i_fieldType = nullableType (fieldType field)
, i_fieldNullability = NullableGADT
, i_fieldDefaultValue = fmap DefaultValue.coerceDefaultValue (i_fieldDefaultValue field)
, i_fieldDescription = fieldDescription field
, i_fieldTableConstraints = i_fieldTableConstraints field
, i_fieldIdentity = i_fieldIdentity 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 conversion fieldDef =
fieldDef
{ i_fieldType = conversion (i_fieldType fieldDef)
, i_fieldDefaultValue = fmap DefaultValue.coerceDefaultValue (i_fieldDefaultValue 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 =>
FieldDefinition nullability a ->
FieldDefinition nullability b
coerceField =
convertField
(SqlType.convertSqlType Coerce.coerce 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 defaultValue fieldDef =
fieldDef
{ i_fieldDefaultValue = Just 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 fieldDef =
fieldDef
{ i_fieldDefaultValue = Nothing
}
{- | Use the supplied options to mark a column as an identity column.
@since 1.1.0.0
-}
markAsIdentity :: FieldIdentityGeneration -> FieldDefinition NotNull a -> FieldDefinition NotNull a
markAsIdentity identityGen fieldDef =
fieldDef
{ i_fieldIdentity = IsIdentityGADT identityGen
}
{- | Remove the identity portion of a field. Note that if a field
@since 1.1.0.0
-}
unmarkIdentity :: FieldDefinition NotNull a -> FieldDefinition NotNull a
unmarkIdentity fieldDef =
fieldDef
{ i_fieldIdentity = AllowedIdentityButNotSetGADT
}
{- | 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 prefix fieldDef =
fieldDef
{ i_fieldName = byteStringToFieldName (B8.pack prefix <> "_" <> fieldNameToByteString (fieldName 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 fieldDef =
Expr.setColumn
(Expr.unqualified (fieldColumnName fieldDef))
. fieldValueToSqlValue fieldDef
{- | Operator alias for 'setField'.
@since 1.0.0.0
-}
(.:=) :: FieldDefinition nullability a -> a -> Expr.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 = SqlComparable.equals
{- | Operator alias for 'fieldEquals'.
@since 1.0.0.0
-}
(.==) :: FieldDefinition nullability a -> a -> Expr.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 = SqlComparable.notEquals
{- | Operator alias for 'fieldNotEquals'.
@since 1.0.0.0
-}
(./=) :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
(./=) = fieldNotEquals
infixl 9 ./=
{- | Checks that the value in a field is distinct from a particular value.
@since 1.1.0.0
-}
fieldIsDistinctFrom :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
fieldIsDistinctFrom = SqlComparable.isDistinctFrom
{- | Checks that the value in a field is not distinct from a particular value.
@since 1.1.0.0
-}
fieldIsNotDistinctFrom :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
fieldIsNotDistinctFrom = SqlComparable.isNotDistinctFrom
{- | 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 = SqlComparable.greaterThan
{- | Operator alias for 'fieldGreaterThan'.
@since 1.0.0.0
-}
(.>) :: FieldDefinition nullability a -> a -> Expr.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 = SqlComparable.lessThan
{- | Operator alias for 'fieldLessThan'.
@since 1.0.0.0
-}
(.<) :: FieldDefinition nullability a -> a -> Expr.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 = SqlComparable.greaterThanOrEqualTo
{- | Operator alias for 'fieldGreaterThanOrEqualTo'.
@since 1.0.0.0
-}
(.>=) :: FieldDefinition nullability a -> a -> Expr.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 = SqlComparable.lessThanOrEqualTo
{- | Operator alias for 'fieldLessThanOrEqualTo'.
@since 1.0.0.0
-}
(.<=) :: FieldDefinition nullability a -> a -> Expr.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 = SqlComparable.like
{- | 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 = SqlComparable.likeInsensitive
{- | Checks that the value in a field is null.
@since 1.0.0.0
-}
fieldIsNull :: FieldDefinition Nullable a -> Expr.BooleanExpr
fieldIsNull = SqlComparable.isNull
{- | Checks that the value in a field is not null.
@since 1.0.0.0
-}
fieldIsNotNull :: FieldDefinition Nullable a -> Expr.BooleanExpr
fieldIsNotNull = SqlComparable.isNotNull
{- | Checks that a field matches a list of values.
@since 1.0.0.0
-}
fieldIn :: FieldDefinition nullability a -> NonEmpty a -> Expr.BooleanExpr
fieldIn = SqlComparable.isIn
{- | Operator alias for 'fieldIn'.
@since 1.0.0.0
-}
(.<-) :: FieldDefinition nullability a -> NonEmpty a -> Expr.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 = SqlComparable.isNotIn
{- | Operator alias for 'fieldNotIn'.
@since 1.0.0.0
-}
(.</-) :: FieldDefinition nullability a -> NonEmpty a -> Expr.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 =
SqlComparable.tupleIn
{- | 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 = SqlComparable.tupleNotIn
{- | 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 columnComparison fieldDef =
columnComparison
(SqlComparable.referenceValueExpression fieldDef)
. fieldValueToExpression fieldDef
{- | Orders a query by the column name for the given field. This is a type-specific
alias for 'SqlComparable.orderBySqlComparable'
@since 1.0.0.0
-}
orderByField ::
FieldDefinition nullability value ->
Expr.OrderByDirection ->
Expr.OrderByExpr
orderByField =
SqlComparable.orderBySqlComparable
{- | A type class that allows different types to be used as qualifiers for column names.
This is often used in conjuction with 'QualifiedFieldDefinition' to represent a
'FieldDefinition' whose reference needs to be qualified in a SQL query involving
multiple tables.
Instances are provided for table 'TableIdentifier.TableIdentifier' and
'AliasName.AliasName' so that either can be used a qualifiers for fields.
@since 1.1.0.0
-}
class FieldQualifier qualifier where
qualifyColumnName :: qualifier -> Expr.ColumnName -> Expr.Qualified Expr.ColumnName
{- |
@since 1.1.0.0
-}
instance FieldQualifier TableIdentifier.TableIdentifier where
qualifyColumnName tableId =
Expr.qualifyColumn
(TableIdentifier.tableIdSchemaName tableId)
(TableIdentifier.tableIdUnqualifiedName tableId)
{- |
@since 1.1.0.0
-}
instance FieldQualifier AliasName.AliasName where
qualifyColumnName =
Expr.aliasQualifyColumn . AliasName.aliasNameToAliasExpr
{- | A 'FieldDefinition' paired with a 'qualifier' so SQL references to the field
will be qualified. A 'SqlComparable.SqlComparable' instance is provided so that
'QualifiedFieldDefinition' can be used with the SQL comparison and ordering
functions offered by 'Sql.Comparable.SqlComparable'.
See Also: 'qualifyField', 'FieldQualifier'
@since 1.1.0.0
-}
data QualifiedFieldDefinition qualifier nullability a = QualifiedFieldDefinition
{ i_fieldQualifier :: qualifier
, i_unqualifiedDefinition :: FieldDefinition nullability a
}
{- | Constructs a 'QualifiedFieldDefinition' that will qualify the field with
the specified qualifier. Generally speaking, the 'qualifier' will need to
provide an instance of 'FieldQualifier' for the constructed 'QualifiedFieldDefinition'
to be of use.
@since 1.1.0.0
-}
qualifyField ::
qualifier ->
FieldDefinition nullability a ->
QualifiedFieldDefinition qualifier nullability a
qualifyField =
QualifiedFieldDefinition
{- | Generates the SQL expression to refer to the column represented by a
'QualifiedFieldDefinition'. The column will be qualified using the 'qualifier'
that was passed to 'qualifyField'.
@since 1.1.0.0
-}
qualifiedFieldColumnName ::
FieldQualifier qualifier =>
QualifiedFieldDefinition qualifier nullability a ->
Expr.Qualified Expr.ColumnName
qualifiedFieldColumnName qualifiedFieldDef =
qualifyColumnName
(i_fieldQualifier qualifiedFieldDef)
(fieldColumnName (i_unqualifiedDefinition qualifiedFieldDef))
{- |
@since 1.1.0.0
-}
instance FieldQualifier qualifier => SqlComparable.SqlComparable (QualifiedFieldDefinition qualifier nullability a) a where
toComparableSqlValue =
toComparableSqlValue . i_unqualifiedDefinition
referenceValueExpression =
Expr.columnReference . Expr.untrackQualified . qualifiedFieldColumnName
{- | INTERNAL: This type is an internal tracking of if a column is an identity column. We tie this to
the nullability because a nullable column is not allowed to be an identity column.
@since 1.1.0.0
-}
data IdentityGADT nullability where
IsIdentityGADT :: FieldIdentityGeneration -> IdentityGADT NotNull
AllowedIdentityButNotSetGADT :: IdentityGADT NotNull
NotIdentityGADT :: IdentityGADT Nullable
{- | Get the 'FieldIdentityGeneration', if there is one, of a 'FieldDefinition'.
@since 1.1.0.0
-}
fieldIdentityGeneration :: FieldDefinition nullability a -> Maybe FieldIdentityGeneration
fieldIdentityGeneration fieldDef =
case i_fieldIdentity fieldDef of
AllowedIdentityButNotSetGADT -> Nothing
NotIdentityGADT -> Nothing
IsIdentityGADT colId -> Just colId
{- | Represents how the identity field be will generated.
@since 1.1.0.0
-}
data FieldIdentityGeneration
= -- | The field will always be generated, and user supplied values during write for it is expressly not allowed.
--
-- @since 1.1.0.0
GeneratedAlways
| -- | The field will be generated by default, allowing for user defined values for writes will be allowed.
--
-- @since 1.1.0.0
GeneratedByDefault
deriving
( -- | @since 1.1.0.0
Eq
)