Skip to content

Split up PersistEntity #1321

@parsonsmatt

Description

@parsonsmatt

Related to #1037 and #1239

Right now, PersistEntity has a ton of responsibilities. Here's the class definition (minus comments)

class 
    ( PersistField (Key record), ToJSON (Key record), FromJSON (Key record)
    , Show (Key record), Read (Key record), Eq (Key record), Ord (Key record)
    )
  => 
    PersistEntity record 
  where
    type PersistEntityBackend record

    data Key record
    keyToValues :: Key record -> [PersistValue]
    keyFromValues :: [PersistValue] -> Either Text (Key record)
    persistIdField :: EntityField record (Key record)

    entityDef :: proxy record -> EntityDef

    data EntityField record :: Type -> Type
    persistFieldDef :: EntityField record typ -> FieldDef
    toPersistFields :: record -> [SomePersistField]
    fromPersistValues :: [PersistValue] -> Either Text record

    data Unique record
    persistUniqueKeys :: record -> [Unique record]
    persistUniqueToFieldNames :: Unique record -> NonEmpty (FieldNameHS, FieldNameDB)
    persistUniqueToValues :: Unique record -> [PersistValue]

    fieldLens :: EntityField record field
              -> (forall f. Functor f => (field -> f field) -> Entity record -> f (Entity record))

    keyFromRecordM :: Maybe (record -> Key record)
    keyFromRecordM = Nothing

So, a PersistEntity record implies:

  • There's a specific PersistEntityBackend record that the type must be used with (probably can be deleted with Deprecate mpsGeneric #1250 )
  • There's a primary Key for the record.
    • That key can be converted into a [PersistValue], and parsed from a [PersistValue]
    • There's a specific EntityField that directly corresponds to that primary key (this is false for composite keys)
  • You can get an entityDef Proxy :: EntityDef for a given type.
  • There is an EntityField instance for the type.
  • You can convert the type into a [SomePersistField] and parse one out of a [PersistValue].
  • The record has uniqueness keys. Rethink Unique #1239
    • Uniqueness keys can be converted into a [PersistValue] (but not parsed - there's no [PersistValue] -> Either Text (Unique record)?)
    • Uniqueness keys have a NonEmpty (FieldNameHS, FieldNameDB) associated - like a FieldDef or EntityDef
    • Primary keys and Unique keys are not interchangeable.

This is too much.

Here's a sketch of how I'd like to refactor this. First, I want to have classes for things that can be rendered into a database with multiple columns, as well as a class for parsing them out.

class ToPersistValues a where
    toPersistValues :: a -> [PersistValue]

class FromPersistValues a where
    -- note that we may want this to be something like Either Text ([PersistValue], a)
    -- if the idea is that we parse multiple columns, then it makes sense to have left overs
    fromPersistValues :: [PersistValue] -> Either Text a    

We can pretty easily have an instance of PersistField a => ToPersistValues (Only a). And we can make tuples for parsing out lots of stuff.

In #1133 I talk about using prairie, which pulls out EntityField, persistFieldLens, and other things from PersistEntity. That may not be feasible until we get the separation of types for inserting/reading from the database. I'm not sure if that's necessary.

Then that leaves our Key and Unique stuff. #1239 I think I'll put most of my thoughts in there about how to resolve it.


So, I think, for a path forward:

  1. Implement the To/FromPersistValues classes and make them superclasses of PersistEntity.
  2. Figure out the uniqueness keys design
    a. Do we want to make that a superclass? It doesn't make sense for all things... it is perfectly possible in SQL to have a table without a primary key.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions