orville-postgresql-1.0.0.0: src/Orville/PostgreSQL/Marshall/FieldDefinition.hs
{-# 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
{ 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]
}
{- |
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 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 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 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 =
Expr.columnReference . 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 fieldDef =
Expr.columnDefinition
(fieldColumnName fieldDef)
(SqlType.sqlTypeExpr $ fieldType fieldDef)
(Just $ fieldColumnConstraint fieldDef)
(fmap (Expr.columnDefault . DefaultValue.defaultValueExpression) $ i_fieldDefaultValue 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 fieldDef =
case fieldNullability fieldDef of
NotNullField _ ->
Expr.notNullConstraint
NullableField _ ->
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.
@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.
@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.
@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
}
{- |
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 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
}
{- |
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 = \sqlValue -> 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
}
{- |
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, Coerce.Coercible b a) =>
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
}
{- |
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 value =
Expr.setColumn
(fieldColumnName fieldDef)
(fieldValueToSqlValue fieldDef value)
{- |
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 =
whereColumnComparison Expr.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 =
whereColumnComparison Expr.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 greater than a particular value.
@since 1.0.0.0
-}
fieldGreaterThan :: FieldDefinition nullability a -> a -> Expr.BooleanExpr
fieldGreaterThan =
whereColumnComparison Expr.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 =
whereColumnComparison Expr.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 =
whereColumnComparison Expr.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 =
whereColumnComparison Expr.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 fieldDef likePattern =
Expr.like
(fieldColumnReference fieldDef)
(Expr.valueExpression (SqlValue.fromText 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 fieldDef likePattern =
Expr.likeInsensitive
(fieldColumnReference fieldDef)
(Expr.valueExpression (SqlValue.fromText likePattern))
{- |
Checks that the value in a field is null.
@since 1.0.0.0
-}
fieldIsNull :: FieldDefinition Nullable a -> Expr.BooleanExpr
fieldIsNull =
Expr.isNull . fieldColumnReference
{- |
Checks that the value in a field is not null.
@since 1.0.0.0
-}
fieldIsNotNull :: FieldDefinition Nullable a -> Expr.BooleanExpr
fieldIsNotNull =
Expr.isNotNull . fieldColumnReference
{- |
Checks that a field matches a list of values.
@since 1.0.0.0
-}
fieldIn :: FieldDefinition nullability a -> NonEmpty a -> Expr.BooleanExpr
fieldIn fieldDef values =
Expr.valueIn
(fieldColumnReference fieldDef)
(fmap (fieldValueToExpression fieldDef) values)
{- |
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 fieldDef values =
Expr.valueNotIn
(fieldColumnReference fieldDef)
(fmap (fieldValueToExpression fieldDef) values)
{- |
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 fieldDefA fieldDefB values =
Expr.tupleIn
(fieldColumnReference fieldDefA :| [fieldColumnReference fieldDefB])
(fmap (toSqlValueTuple fieldDefA fieldDefB) 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 fieldDefA fieldDefB values =
Expr.tupleNotIn
(fieldColumnReference fieldDefA :| [fieldColumnReference fieldDefB])
(fmap (toSqlValueTuple fieldDefA fieldDefB) 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 fieldDefA fieldDefB (a, b) =
fieldValueToExpression fieldDefA a
:| [fieldValueToExpression fieldDefB 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 columnComparison fieldDef a =
columnComparison
(fieldColumnReference fieldDef)
(fieldValueToExpression fieldDef a)
{-- |
Orders a query by the column name for the given field.
--}
orderByField ::
FieldDefinition nullability value ->
Expr.OrderByDirection ->
Expr.OrderByExpr
orderByField =
Expr.orderByColumnName . fieldColumnName