HORIZON HASKELLDocslts/ghc-9.10.x248f8f02026-10-05Search names, modules, packages, or :: a typeCtrl K

GHC 9.10.3 · lts/ghc-9.10.x · 248f8f0 · 2026-10-05

Modulepersistent-2.14.6.3Haskell2010

Database.Persist.Class.PersistUnique

  • 2 types
  • 4 classes
  • 13 values
classclass PersistStoreRead backend => PersistUniqueRead backend where
#

Queries against Unique keys (other than the id Key).

Please read the general Persistent documentation to learn how to create Unique keys.

Using this with an Entity without a Unique key leads to undefined behavior. A few of these functions require a single Unique, so using an Entity with multiple Uniques is also undefined. In these cases persistent's goal is to throw an exception as soon as possible, but persistent is still transitioning to that.

SQL backends automatically create uniqueness constraints, but for MongoDB you must manually place a unique index on a field to have a uniqueness constraint.

Methods

  • getBy :: (MonadIO m, PersistRecordBackend record backend) => Unique record -> ReaderT backend m (Maybe (Entity record))

    Get a record by unique key, if available. Returns also the identifier.

    Example usage

    With schema-1 and dataset-1:

    getBySpjName :: MonadIO m  => ReaderT SqlBackend m (Maybe (Entity User))
    getBySpjName = getBy $ UniqueUserName "SPJ"
    mSpjEnt <- getBySpjName

    The above query when applied on dataset-1, will get this entity:

    +----+------+-----+
    | id | name | age |
    +----+------+-----+
    |  1 | SPJ  |  40 |
    +----+------+-----+
  • existsBy :: (MonadIO m, PersistRecordBackend record backend) => Unique record -> ReaderT backend m Bool

    Returns True if a record with this unique key exists, otherwise False.

    Example usage

    With schema-1 and dataset-1:

    existsBySpjName :: MonadIO m  => ReaderT SqlBackend m Bool
    existsBySpjName = existsBy $ UniqueUserName "SPJ"
    spjEntExists <- existsBySpjName

    The above query when applied on dataset-1, will return the value True.

Instances4PersistUniqueRead
classclass (PersistUniqueRead backend, PersistStoreWrite backend) => PersistUniqueWrite backend where
#

Some functions in this module (insertUnique, insertBy, and replaceUnique) first query the unique indexes to check for conflicts. You could instead optimistically attempt to perform the operation (e.g. replace instead of replaceUnique). However,

  • there is some fragility to trying to catch the correct exception and determing the column of failure;

  • an exception will automatically abort the current SQL transaction.

Methods

  • deleteBy :: (MonadIO m, PersistRecordBackend record backend) => Unique record -> ReaderT backend m ()

    Delete a specific record by unique key. Does nothing if no record matches.

    Example usage

    With schema-1 and dataset-1,

    deleteBySpjName :: MonadIO m => ReaderT SqlBackend m ()
    deleteBySpjName = deleteBy UniqueUserName "SPJ"

    The above query when applied on dataset-1, will produce this:

    +-----+------+-----+
    |id   |name  |age  |
    +-----+------+-----+
    |2    |Simon |41   |
    +-----+------+-----+
  • insertUnique :: (MonadIO m, PersistRecordBackend record backend, SafeToInsert record) => record -> ReaderT backend m (Maybe (Key record))

    Like insert, but returns Nothing when the record couldn't be inserted because of a uniqueness constraint.

    Example usage

    With schema-1 and dataset-1, we try to insert the following two records:

    linusId <- insertUnique $ User "Linus" 48
    spjId   <- insertUnique $ User "SPJ" 90
    +-----+------+-----+
    |id   |name  |age  |
    +-----+------+-----+
    |1    |SPJ   |40   |
    +-----+------+-----+
    |2    |Simon |41   |
    +-----+------+-----+
    |3    |Linus |48   |
    +-----+------+-----+

    Linus's record was inserted to dataset-1, while SPJ wasn't because SPJ already exists in dataset-1.

  • insertUnique_ :: (MonadIO m, PersistRecordBackend record backend, SafeToInsert record) => record -> ReaderT backend m (Maybe ())

    Same as insertUnique but doesn't return a Key.

    Example usage

    With schema-1 and dataset-1, we try to insert the following two records:

    linusId <- insertUnique_ $ User "Linus" 48
    spjId   <- insertUnique_ $ User "SPJ" 90
    +-----+------+-----+
    |id   |name  |age  |
    +-----+------+-----+
    |1    |SPJ   |40   |
    +-----+------+-----+
    |2    |Simon |41   |
    +-----+------+-----+
    |3    |Linus |48   |
    +-----+------+-----+

    Linus's record was inserted to dataset-1, while SPJ wasn't because SPJ already exists in dataset-1.

  • upsert :: (MonadIO m, PersistRecordBackend record backend, OnlyOneUniqueKey record, SafeToInsert record) => record -> [Update record] -> ReaderT backend m (Entity record)

    Update based on a uniqueness constraint or insert:

    • insert the new record if it does not exist;

    • If the record exists (matched via it's uniqueness constraint), then update the existing record with the parameters which is passed on as list to the function.

    Example usage

    First, we try to explain upsert using schema-1 and dataset-1.

    upsertSpj :: MonadIO m => [Update User] -> ReaderT SqlBackend m (Maybe (Entity User))
    upsertSpj updates = upsert (User "SPJ" 999) updates
    mSpjEnt <- upsertSpj [UserAge +=. 15]

    The above query when applied on dataset-1, will produce this:

    +-----+-----+--------+
    |id   |name |age     |
    +-----+-----+--------+
    |1    |SPJ  |40 -> 55|
    +-----+-----+--------+
    |2    |Simon|41      |
    +-----+-----+--------+
    upsertX :: MonadIO m => [Update User] -> ReaderT SqlBackend m (Maybe (Entity User))
    upsertX updates = upsert (User "X" 999) updates
    mXEnt <- upsertX [UserAge +=. 15]

    The above query when applied on dataset-1, will produce this:

    +-----+-----+--------+
    |id   |name |age     |
    +-----+-----+--------+
    |1    |SPJ  |40      |
    +-----+-----+--------+
    |2    |Simon|41      |
    +-----+-----+--------+
    |3    |X    |999     |
    +-----+-----+--------+

    Next, what if the schema has two uniqueness constraints? Let's check it out using schema-2:

    mSpjEnt <- upsertSpj [UserAge +=. 15]

    This fails with a compile-time type error alerting us to the fact that this record has multiple unique keys, and suggests that we look for upsertBy to select the unique key we want.

  • upsertBy :: (MonadIO m, PersistRecordBackend record backend, SafeToInsert record) => Unique record -> record -> [Update record] -> ReaderT backend m (Entity record)

    Update based on a given uniqueness constraint or insert:

    • insert the new record if it does not exist;

    • update the existing record that matches the given uniqueness constraint.

    Example usage

    We try to explain upsertBy using schema-2 and dataset-1.

    upsertBySpjName :: MonadIO m => User -> [Update User] -> ReaderT SqlBackend m (Entity User)
    upsertBySpjName record updates = upsertBy (UniqueUserName "SPJ") record updates
    mSpjEnt <- upsertBySpjName (Person "X" 999) [PersonAge += .15]

    The above query will alter dataset-1 to:

    +-----+-----+--------+
    |id   |name |age     |
    +-----+-----+--------+
    |1    |SPJ  |40 -> 55|
    +-----+-----+--------+
    |2    |Simon|41      |
    +-----+-----+--------+
    upsertBySimonAge :: MonadIO m => User -> [Update User] -> ReaderT SqlBackend m (Entity User)
    upsertBySimonAge record updates = upsertBy (UniqueUserName "SPJ") record updates
    mPhilipEnt <- upsertBySimonAge (User "X" 999) [UserName =. "Philip"]

    The above query will alter dataset-1 to:

    +----+-----------------+-----+
    | id |      name       | age |
    +----+-----------------+-----+
    |  1 | SPJ             |  40 |
    +----+-----------------+-----+
    |  2 | Simon -> Philip |  41 |
    +----+-----------------+-----+
    upsertByUnknownName :: MonadIO m => User -> [Update User] -> ReaderT SqlBackend m (Entity User)
    upsertByUnknownName record updates = upsertBy (UniqueUserName "Unknown") record updates
    mXEnt <- upsertByUnknownName (User "X" 999) [UserAge +=. 15]

    This query will alter dataset-1 to:

    +-----+-----+-----+
    |id   |name |age  |
    +-----+-----+-----+
    |1    |SPJ  |40   |
    +-----+-----+-----+
    |2    |Simon|41   |
    +-----+-----+-----+
    |3    |X    |999  |
    +-----+-----+-----+
  • putMany :: (MonadIO m, PersistRecordBackend record backend, SafeToInsert record) => [record] -> ReaderT backend m ()

    Put many records into db

    • insert new records that do not exist (or violate any unique constraints)

    • replace existing records (matching any unique constraint)

Instances3PersistUniqueWrite
classclass PersistEntity record => OnlyOneUniqueKey record where
#

This class is used to ensure that upsert is only called on records that have a single Unique key. The quasiquoter automatically generates working instances for appropriate records, and generates TypeError instances for records that have 0 or multiple unique keys.

Methods

classclass PersistEntity record => AtLeastOneUniqueKey record where
#

This class is used to ensure that functions requring at least one unique key are not called with records that have 0 unique keys. The quasiquoter automatically writes working instances for appropriate entities, and generates TypeError instances for records that have 0 unique keys.

Methods

typetype MultipleUniqueKeysError ty = ((('Text "The entity " ':<>: 'ShowType ty) ':<>: 'Text " has multiple unique keys.") ':$$: ('Text "The function you are trying to call requires only a single " ':<>: 'Text "unique key.")) ':$$: (('Text "There is probably a variant of the function with 'By' " ':<>: 'Text "appended that will allow you to select a unique key ") ':<>: 'Text "for the operation.")
#

This is an error message. It is used when an entity has multiple unique keys, and the function expects a single unique key.

valuegetByValue
  1. :: (MonadIO m, PersistUniqueRead backend, PersistRecordBackend record backend, AtLeastOneUniqueKey record)
  2. => record
  3. -> ReaderT backend m (Maybe (Entity record))
#

A modification of getBy, which takes the PersistEntity itself instead of a Unique record. Returns a record matching one of the unique keys. This function makes the most sense on entities with a single Unique constructor.

Example usage

With schema-1 and dataset-1,

getBySpjValue :: MonadIO m => ReaderT SqlBackend m (Maybe (Entity User)) getBySpjValue = getByValue $ User SPJ 999

mSpjEnt <- getBySpjValue

The above query when applied on dataset-1, will get this record:

+----+------+-----+
| id | name | age |
+----+------+-----+
|  1 | SPJ  |  40 |
+----+------+-----+
valueinsertBy
  1. :: (MonadIO m, PersistUniqueWrite backend, PersistRecordBackend record backend, AtLeastOneUniqueKey record, SafeToInsert record)
  2. => record
  3. -> ReaderT backend m (Either (Entity record) (Key record))
#

Insert a value, checking for conflicts with any unique constraints. If a duplicate exists in the database, it is returned as Left. Otherwise, the new 'Key is returned as Right.

Example usage

With schema-2 and dataset-1, we have following lines of code:

l1 <- insertBy $ User "SPJ" 20
l2 <- insertBy $ User "XXX" 41
l3 <- insertBy $ User "SPJ" 40
r1 <- insertBy $ User "XXX" 100

First three lines return Left because there're duplicates in given record's uniqueness constraints. While the last line returns a new key as Right.

valueinsertUniqueEntity
  1. :: (MonadIO m, PersistRecordBackend record backend, PersistUniqueWrite backend, SafeToInsert record)
  2. => record
  3. -> ReaderT backend m (Maybe (Entity record))
#

Like insertEntity, but returns Nothing when the record couldn't be inserted because of a uniqueness constraint.

Example usage

We use schema-2 and dataset-1 here.

insertUniqueSpjEntity :: MonadIO m => ReaderT SqlBackend m (Maybe (Entity User))
insertUniqueSpjEntity = insertUniqueEntity $ User "SPJ" 50
mSpjEnt <- insertUniqueSpjEntity

The above query results Nothing as SPJ already exists.

insertUniqueAlexaEntity :: MonadIO m => ReaderT SqlBackend m (Maybe (Entity User))
insertUniqueAlexaEntity = insertUniqueEntity $ User "Alexa" 3
mAlexaEnt <- insertUniqueSpjEntity

Because there's no such unique keywords of the given record, the above query when applied on dataset-1, will produce this:

+----+-------+-----+
| id | name  | age |
+----+-------+-----+
|  1 | SPJ   |  40 |
+----+-------+-----+
|  2 | Simon |  41 |
+----+-------+-----+
|  3 | Alexa |   3 |
+----+-------+-----+
valuecheckUnique
  1. :: (MonadIO m, PersistRecordBackend record backend, PersistUniqueRead backend)
  2. => record
  3. -> ReaderT backend m (Maybe (Unique record))
#

Check whether there are any conflicts for unique keys with this entity and existing entities in the database.

Returns Nothing if the entity would be unique, and could thus safely be inserted. on a conflict returns the conflicting key

Example usage

We use schema-1 and dataset-1 here.

This would be Nothing:

mAlanConst <- checkUnique $ User "Alan" 70

While this would be Just because SPJ already exists:

mSpjConst <- checkUnique $ User "SPJ" 60
valuecheckUniqueUpdateable
  1. :: (MonadIO m, PersistRecordBackend record backend, PersistUniqueRead backend)
  2. => Entity record
  3. -> ReaderT backend m (Maybe (Unique record))
#

Check whether there are any conflicts for unique keys with this entity and existing entities in the database.

Returns Nothing if the entity would stay unique, and could thus safely be updated. on a conflict returns the conflicting key

This is similar to checkUnique, except it's useful for updating - when the particular entity already exists, it would normally conflict with itself. This variant ignores those conflicts

Example usage

We use schema-1 and dataset-1 here.

This would be Nothing:

mAlanConst <- checkUnique $ User "Alan" 70

While this would be Just because SPJ already exists:

mSpjConst <- checkUnique $ User "SPJ" 60
valueonlyUnique
  1. :: (MonadIO m, PersistUniqueWrite backend, PersistRecordBackend record backend, OnlyOneUniqueKey record)
  2. => record
  3. -> ReaderT backend m (Unique record)
#

Return the single unique key for a record.

Example usage

We use shcema-1 and dataset-1 here.

onlySimonConst :: MonadIO m => ReaderT SqlBackend m (Unique User)
onlySimonConst = onlyUnique $ User "Simon" 999
mSimonConst <- onlySimonConst

mSimonConst would be Simon's uniqueness constraint. Note that onlyUnique doesn't work if there're more than two constraints. It will fail with a type error instead.

valuedefaultUpsertBy
  1. :: (PersistEntityBackend record ~ BaseBackend backend, PersistEntity record, MonadIO m, PersistStoreWrite backend, PersistUniqueRead backend, SafeToInsert record)
  2. => Unique record

    uniqueness constraint to find by

  3. -> record

    new record to insert

  4. -> [Update record]

    updates to perform if the record already exists

  5. -> ReaderT backend m (Entity record)

    the record in the database after the operation

#

The slow but generic upsertBy implementation for any PersistUniqueRead. * Lookup corresponding entities (if any) getBy. * If the record exists, update using updateGet. * If it does not exist, insert using insertEntity. @since 2.11