{-# 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 , (. 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 '. 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 -} (. NonEmpty a -> Expr.BooleanExpr (. 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