Skip to content

Backend compatible#886

Merged
parsonsmatt merged 28 commits intoyesodweb:vlix/backend-compatiblefrom
Vlix:backend-compatible
Apr 15, 2019
Merged

Backend compatible#886
parsonsmatt merged 28 commits intoyesodweb:vlix/backend-compatiblefrom
Vlix:backend-compatible

Conversation

@Vlix
Copy link
Contributor

@Vlix Vlix commented Apr 12, 2019

I've factored out what I've found to be redundant constraints in some SQL functions.
Most notably all instances of IsSqlBackend have been replaced or removed and BackendCompatible SqlBackend has been integrated where possible.

I've noticed a ton of redundant (at least seemingly) classes/types/etc.
After this refactor at least the following aren't noticeably used anywhere:

  • SqlReadBackend
  • SqlWriteBackend
  • IsPersistBackend
  • IsSqlBackend

If anyone can look this over, to see if I didn't interfere with the API, or some guarantee that's lost by fiddling with constraints, that'd be great.

Vlix and others added 27 commits March 1, 2018 14:21
* Upgraded the randomValues function in DataTestTypes.hs to actually have random values everytime
* small OCD
@parsonsmatt
Copy link
Collaborator

parsonsmatt commented Apr 12, 2019

SqlReadBackend and SqlWriteBackend are used by clients of the library. I know they're used extensively by Freckle, so as long as those types are still working good well after these changes then we should be good to go.

@Vlix
Copy link
Contributor Author

Vlix commented Apr 12, 2019

SqlReadBackend and SqlWriteBackend are used by clients of the library. I know they're used extensively by Freckle, so as long as those types are still working good well after these changes then we should be good to go.

If someone can check with whatever uses it, that'd be great, because I have no idea how they'd be used looking at this library alone.

@bitemyapp
Copy link
Contributor

bitemyapp commented Apr 12, 2019

@akurilin I suggest building this against your org repo. I can't do it for you. I remember y'all using those types too but I have no idea where things are at now.

@parsonsmatt
Copy link
Collaborator

I'm gonna write some tests for the feature.

@Vlix
Copy link
Contributor Author

Vlix commented Apr 12, 2019

Ok, well if the Read/Write types have a function, that still leaves the IsPersistBackend (and consequently IsSqlBackend) to not be used anywhere. If it is decided they are actually redundant, I can remove the definitions and instances of them.

Seeing as this change does shift almost all functionality to BackendCompatible, instead of HasPersistBackend, would that break people's programs?
It'd probably just give type errors when they update their persistent dependency, right?

@parsonsmatt
Copy link
Collaborator

The BackendCompatible stuff is used so that SqlReadBackend can be used for persistent entities that are defined for SqlBackend but only in read queries. Note that SqlReadBackend doesn't have any instances for the Write classes, so it can't do any updates to the DB. This is used to give certain parts of the program read-only access to the database. I wrote that class because the IsSqlBackend stuff was too restrictive for persistent-typed-db, where I had PersistEntityBackend record ~ SqlFor SomeDb.

I'd love it if the new changes were mostly "drop in." I think changing the functions to return a concrete Sqlbackend and then allowing clients to apply those functions manually will probably be a good thing - should make type inference a lot better and error messages easier to follow.

@parsonsmatt
Copy link
Collaborator

I think it may be a good idea to deprecate the IsSqlBackend-related stuff so we can give good deprecation warnings for an upgrade/migration path. ideally we only have one mechanism for saying "This backend is compatible with this other backend."

runSqlite :: (MonadUnliftIO m)
=> Text -- ^ connection string
-> ReaderT backend (NoLoggingT (ResourceT m)) a -- ^ database action
-> ReaderT SqlBackend (NoLoggingT (ResourceT m)) a -- ^ database action
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another thing that should be easy to generalize on our side.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, no, actually 😛

The entire point it that the backend/SqlBackend here, is from the open' function, which creates the SqlBackend in the withSqliteConn. Making a SqlBackend into a BackendCompatible SqlBackend backend => backend would imply creating a backend from a SqlBackend. That being annoying and undesirable was the entire point.
Remember, this is Sqlite specific, so it's gonna have to work with a SqlBackend.

The RawSqlite PR should work now afaict, though. Then the SqlBackend here would turn into a RawSqlite SqlBackend which is what merijn was trying to accomplish, I think.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not quite right - BackendCompatible SqlBackend backend implies that you can create a SqlBackend from a backend. runSqlite would be able to run an action that uses SqlBackend, or SqlReadBackend, or SqlFor SomeDb, or RawSqlite SqlBackend (provided that was given an instance BackendCompatible SqlBackend (RawSqlite SqlBackend)).

The problem with IsSqlBackend is that it requires an isomorphism - that you can go from a SqlBackend to your type. RawSqlite SqlBackend has more than a mere SqlBackend, so there's no SqlBackend -> RawSqlite SqlBackend you can use.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can try the polymorphic BackendCompatbile SqlBackend backend => backend stuff and if it presents problems with the raw SQLite stuff then we can revert those functions to concrete types.

It's not a deal breaker either way - requiring ReaderT SqlBackend ... just means that library users call local SqlReadBackend to map the types how they want instead of the library doing it automatically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try it one more time, maybe I'm just derping with the type system.

-- Returns also the identifiers.
selectSource
:: (PersistQueryRead (BaseBackend backend), MonadResource m, PersistEntity record, PersistEntityBackend record ~ BaseBackend (BaseBackend backend), MonadReader backend m, HasPersistBackend backend)
:: (PersistQueryRead backend, MonadResource m, PersistEntity record, PersistRecordBackend record backend, MonadReader backend m)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This signature is so much nicer ❤️


createSqlPool
:: (MonadLogger m, MonadUnliftIO m, IsSqlBackend backend)
:: (MonadLogger m, MonadUnliftIO m, BackendCompatible SqlBackend backend)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, if these functions can return a BackendCompatible SqlBackend backend => backend then shouldn't the other {with,create}{Postgresql,MySQL,etc}Pool functions also be able to create that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, no, since those functions are actual implementations, they will create SqlBackends.

The only thing I can think of that wouldn't just imply using IsSqlBackend, would be an extra argument somewhere in which the user of the function can give the following function which will be called before creating the Pool:
BackendCompatible SqlBackend backend => SqlBackend -> backend
Maybe with a MonadIO m so they can use IO to create their own backend.

@akurilin
Copy link

@akurilin I suggest building this against your org repo. I can't do it for you. I remember y'all using those types too but I have no idea where things are at now.

@jdreaver is taking a look 👍

@parsonsmatt
Copy link
Collaborator

I added some tests in #887 though they're not super complete.

@Vlix
Copy link
Contributor Author

Vlix commented Apr 13, 2019

Ok, so here's the dealio:

This is the chain:

wrapConnectionInfo :: SqliteConnectionInfo -> Sqlite.Connection -> LogFunc -> IO SqlBackend
wrapConnectionInfo = {the entire SqlBackend is constructed here}

open' :: SqliteConnectionInfo -> LogFunc -> IO SqlBackend
open' connInfo logFunc = do
    conn <- Sqlite.open $ _sqlConnectionStr connInfo
    wrapConnectionInfo connInfo conn logFunc `E.onException` Sqlite.close conn

withSqliteConnInfo :: (MonadUnliftIO m, MonadLogger m)
                   => SqliteConnectionInfo -> (SqlBackend -> m a) -> m a
withSqliteConnInfo = withSqlConn . open'

withSqlConn
    :: (MonadUnliftIO m, MonadLogger m, BackendCompatible SqlBackend backend)
    => (LogFunc -> IO backend) -> (backend -> m a) -> m a
withSqlConn open f = do
    logFunc <- askLogFunc
    withRunInIO $ \run -> bracket
      (open logFunc)
      close'
      (run . f)

runSqlite :: (MonadUnliftIO m)
          => Text -- ^ connection string
          -> ReaderT SqlBackend (NoLoggingT (ResourceT m)) a -- ^ database action
          -> m a
runSqlite connstr action = runResourceT . runNoLoggingT $
    withSqliteConn connstr $ runSqlConn action

runSqlConn takes the action and will run it with whatever the open argument to withSqlConn is. In this case, that's the SqlBackend from open'. (withSqliteConn is just withSqliteConnInfo, but the Text is first turned into a SqliteConnectionInfo)
I honestly don't see a way to make that SqlBackend into anything else? I don't see a way to get around the fact that the database action ReaderT will receive a SqlBackend. What one does with it is up to them, I guess.

@parsonsmatt
Copy link
Collaborator

Okay, let's land this PR, then get the SQLite one working, and I'll give these open-etc functions a try to see if I can get them generalized without borking any expected functionality.

@parsonsmatt
Copy link
Collaborator

parsonsmatt commented Apr 13, 2019

I tried to test on my work codebase but I need a new version of persistent-typed-db for that to work out :) May get that done next week if @jdreaver hasn't reported back.

EDIT: This builds fine on the work codebase, however, we don't use SqlReadT or SqlWriteT anywhere, so not a great point of comparison. I'll try it on esqueleto too, which does use it.

Esqueleto works with this PR.

@parsonsmatt parsonsmatt added this to the 2.10 milestone Apr 13, 2019
@parsonsmatt
Copy link
Collaborator

@Vlix There are some merge conflicts from your other PR. Let's get those resolved and merge this in. Freckle and folks can test on master and if it's not working then we can figure out how to work around it there.

@merijn
Copy link
Contributor

merijn commented Apr 15, 2019

I think it may be a good idea to deprecate the IsSqlBackend-related stuff so we can give good deprecation warnings for an upgrade/migration path. ideally we only have one mechanism for saying "This backend is compatible with this other backend."

Agreed and I'm very happy the (more flexible) BackendCompatible ends up the final winner with this PR :)

@Vlix once this PR is merged I'll try and make some time to revisit the SQLite bits you mentioned, since those needed changing in my other PR and since it's grossly out of date by now I need to revisit it anyway.

@parsonsmatt parsonsmatt changed the base branch from master to vlix/backend-compatible April 15, 2019 21:17
@parsonsmatt parsonsmatt merged commit a52af69 into yesodweb:vlix/backend-compatible Apr 15, 2019
@parsonsmatt
Copy link
Collaborator

I'm going to get this PR finished up

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants