Search code examples
sqliteyesod

What may cause an ErrorConstraint SqliteException error with Yesod + sqlite3?


What may cause the following error?

 uncaught exception: SqliteException (SQLite3 returned ErrorConstraint while attempting to perform step.)

The strange thing is that most of the specs fail with this error that use withApp.

If, however, I modify wipeDB in TestImport.hs to do

system "rm project-name_test.sqlite3*"

then suddenly all specs pass again. Although then the test-run takes about 4-5x as much time, which less than ideal.

Could it be that the wipeDB logic (generated by stack new) is not thorough enough?

I've tried to look in the models file to see if I might have specified uniqueness constraints in there, but I don't see anything out of place.

I've commented everything out of models except for:

User
    emailAddress Text
    password ByteString
    verified Bool
    verifyKey Text
    resetPasswordKey Text
    deriving Show

Foo
    userA UserId
    userB UserId
    deriving Show

If there are constraints, they would be in this file, wouldn't they? Or can there be other places which declare constraints? At any rate, what constraints might the exception refer to?

I haven't narrowed it down as much as I would like to yet, but it seems that all this can be traced back to an runDB $ insert $ Foo, which if I remove, the exception in question is gone, much more specs pass, and only a few fail because they expect the insertion to have taken place. And they too fail with assertion failures, and not the ErrorConstraint exception.

If there are multiple possible reasons for this error, I would like to hear about them as I find the exception to be more vague than I'd like.


Solution

  • This is the best solution I've found. And what looks like will be the "official" solution.

    So in my project i've done this way.

    So this is my wipeDB function now:

    wipeDB :: App -> IO ()
    wipeDB app = do
        let settings = appSettings app
            logFunc = messageLoggerSource app (appLogger app)
    
        sqliteConn <- rawConnection (sqlDatabase $ appDatabaseConf settings)
        let infoNoFK = set fkEnabled False $ mkSqliteConnectionInfo ""
            wrapper = wrapConnectionInfo infoNoFK sqliteConn
        pool <- runLoggingT (createSqlPool wrapper 1) logFunc
    
        flip runSqlPersistMPool pool $ do
            tables <- getTables
            sqlBackend <- ask
            let queries = map (\t -> "DELETE FROM " ++ (connEscapeName sqlBackend $ DBName t)) tables
            forM_ queries (\q -> rawExecute q [])