yesodweb / yesodweb/persistent
Unify approaches to polymorphism
Nobody has claimed this yet.
- Dominant language
- Haskell
- Stars
- 486
- Forks
- 306
- PR merge metrics
- No merged PRs in 30d
Description
Right now, persistent has a somewhat confusing array of techniques for providing polymorphism.
The intent is to allow queries and database actions to be backend agnostic.
f
:: (MonadIO m, PersistStoreRead backend)
=> ReaderT backend m a
This function works for any backend that implements the PersistStoreRead class.
That class has this (simplified) definition:
class PersistStoreRead backend where
get
:: ( MonadIO m
, PersistEntity record
, PersistEntityBackend record ~ backend
)
=> Key entity
-> ReaderT backend m (Maybe record)
This works pretty well - basically every possible persistent backend can support this.
However, we come to a problem with upsert. This is not natively handled by all backends, so we provide somewhat dumb fallbacks, and allow instances to provide better behavior.
Simplifying a bit, we have:
class (PersistStore backend) => PersistUnique backend where
upsertBy
:: (MonadIO m, PersistRecordBackend record backend)
=> Unique record
-> record
-> [Update record]
-> ReaderT backend m (Entity record)
upsertBy = defaultUpsertBy
defaultUpsertBy performs two database actions, while an efficient override might be able to do it in a single database action.
So how does SqlBackend work? Again, simplifying it a bit, we have:
instance PersistUnique SqlBackend where
upsertBy uniqueKey record updates = do
conn <- ask
case connUpsertSql conn of
Nothing ->
defaultUpsertBy uniqueKey record updates
Just upsertSql -> do
-- run the optimized action
So, SqlBackend, as it happens, is not guaranteed to have an efficient implementation of upsert. So we have a record field like:
data SqlBackend = SqlBackend
{ connUpsertSql :: Maybe MkUpsertSql
}
If we're producing a backend for Postgres, which does have an efficient upsert, then we put a Just connUpsertSqlFunction in the record. If we're producing a backend for MySql (which does not yet support it? idk) then we write Nothing for the field, and we use the default slow implementation.
We've now got two approaches for polymorphism - one is adding Maybe fields to a record, and the other is adding a type class for the relevant operations. This is unsatisfying.
Considerations
We want:
- To provide a uniform interface for database access.
- To allow specific database backends to provide more efficient implementations of operations.
- For people to write programs that can operate against different database backends.
What isn't great:
- Lots of different ways to accomplish the same basic thing
- Confusion around how this stuff all works
- Friction around adding new features in a backwards-compatible way.
The PR #1298 adds a new type class and a new record field to SqlBackend to support streaming rows. By all accounts, it's doing everything right - the existing conventions are followed perfectly.
Alternatives
How else can we do this?
We want for eg MongoContext and SqlBackend to work, and we also want for postgresql and mysql to work for upsert, despite sharing a SqlBackend.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the PersistStoreRead and PersistUnique classes, SqlBackend's connUpsertSql field, and PR #1298's streaming-row changes. Compare the type-class and record-field approaches for backend-specific behavior; done means an agreed, documented design that covers MongoContext, SqlBackend, PostgreSQL, and MySQL without leaving the implementation scope ambiguous.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- haskell
- Domain
- backend-api-design, databases
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100