Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test file-based db read/write operations #345

Closed

Conversation

paweljakubas
Copy link
Contributor

@paweljakubas paweljakubas commented May 31, 2019

Issue Number

#154

Overview

  • I have added unit tests that check file-backed sqlite db read/write/clean operation in a number of state changing scenerios
  • I have added property test that checks if we are in line with in-memory version when using random number of state changing operations in a row
  • I have exposed Connection in newDBLayer in order to have a possibility to close it. I presume it will be needed in file-backed db instances

Comments

I have an error - need to investigate it

  1. This one only locally (not in CV) - maybe number of available connections is lower in my computer:
$ stack test --ta '-m "random operation"' cardano-wallet-core:test:unit

       uncaught exception: SqliteException
       SQLite3 returned ErrorCan'tOpen while attempting to perform open "backup/test.db".
       (after 99 tests and 1 shrink)
         KeyValPairs []

but not really confident here as I have :

[pawel@arch cardano-wallet]$ cat /proc/sys/fs/file-max
1628981
  1. This one is both in my computer and CI:
opening and closing of db works FAILED [1]
     test/unit/Cardano/Wallet/DB/SqliteCorruptionSpec.hs:88:9: 
      1) Cardano.Wallet.DB.SqliteCorruption, Check db opening/closing, opening and closing of db works
           uncaught exception: SqliteException
           SQLite3 returned ErrorBusy while attempting to perform close: unable to close due to unfinalized statements or unfinished backups

@paweljakubas paweljakubas requested review from rvl and KtorZ May 31, 2019 05:02
@paweljakubas paweljakubas self-assigned this May 31, 2019
@paweljakubas paweljakubas force-pushed the paweljakubas/154/add-db-read-write-to-file branch 3 times, most recently from 15ed9d9 to 19bb208 Compare May 31, 2019 11:16
Copy link
Contributor

@rvl rvl left a comment

Choose a reason for hiding this comment

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

It looks like a worthy thing to test a little, but not too much.

In the QSM tests I originally had an Open command for testing this type of thing. However I removed it because I confirmed that the wallet process will only ever have a single DBLayer instantiated during its lifetime. So we can assume no concurrent access to .db files, and that the very well tested SQLite library will handle "power cable unplugged" type situations.

@@ -177,17 +178,17 @@ newDBLayer
:: forall s t. (W.IsOurs s, NFData s, Show s, PersistState s, W.TxId t)
=> Maybe FilePath
-- ^ Database file location, or Nothing for in-memory database
-> IO (DBLayer IO s t)
-> IO (Sqlite.Connection, DBLayer IO s t)
newDBLayer fp = do
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps call this newDBLayer' and have newDBLayer = fmap snd . newDBLayer'?

# Ignore everything in this directory
*
# Except this file
!.gitignore
Copy link
Contributor

Choose a reason for hiding this comment

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

I reckon it will be better for tests to create a new directory in /tmp so that they're less susceptible to differences in local state.

close conn


prop_randomOpChunks
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
prop_randomOpChunks
-- This property checks that executing series of wallet operations in a single
-- SQLite session has the same effect as executing the same operations over
-- multiple sessions.
prop_randomOpChunks

unsafeRunExceptT $ createWallet db testPk testCp testMetadata
listWallets db `shouldReturn` [testPk]
close conn
forM_ [1..25] openCloseDB
Copy link
Contributor

Choose a reason for hiding this comment

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

Would replicateM_ 25 openCloseDB be equivalent?

-> s
-> s
-> Expectation
testOpeningCleaning call expectedAfterOpen expectedAfterClean = do
Copy link
Contributor

Choose a reason for hiding this comment

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

I suspect that cleanDB is already pretty well tested because it's run before every DBSpec test case.

But I think the intention of this is to check that changes made with one connection have an effect on the db file, which can be seen from subsequent connections.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cleanDB works fine - I agree, but we had problem with closing DB. See comment below for more details on it

@rvl
Copy link
Contributor

rvl commented Jun 3, 2019

I'm guessing the SQLite ErrorBusy exception is due to the tests not promptly closing the database connection.

@paweljakubas paweljakubas force-pushed the paweljakubas/154/add-db-read-write-to-file branch from 19bb208 to fd3ca18 Compare June 3, 2019 13:22
@paweljakubas
Copy link
Contributor Author

paweljakubas commented Jun 3, 2019

@rvl @KtorZ I made not so small investigation. When we have disk-backed db we should probably be able to close gracefully. I added a number of tests to persuade myself and others that this is the case. A number of things popped out:
Even if we only use newDBLayer and then want to close it we were not able to do it.
This was due to :

enableForeignKeys :: Sqlite.Connection -> IO ()
enableForeignKeys conn = stmt >>= void . Sqlite.step
    where stmt = Sqlite.prepare conn "PRAGMA foreign_keys = ON;"

Sqlite.finalize stmt was missing here.

Moving forward, I was not able to just migrate and addIndexes. I dived deeper and this was probably because of not using withSqlConn in the current version of runQuery' . When using it, close' from Data.Persist.Sql is used and backend is closed afterwards (see implementation). After adopting new version of runQuery' with withSqlConn open/closing roundtrip tests started to pass. This refers to file-based solution.

Still createSqlBackend needs refactoring and we have one property test failing (although open/close tests and unit tests passes) :

Migrating: CREATE TABLE "seq_state_pending_ix"("seq_state_pending_ix_seq_state_id" INTEGER NOT NULL REFERENCES "seq_state","seq_state_pending_ix_index" INTEGER NOT NULL, PRIMARY KEY ("seq_state_pending_ix_seq_state_id","seq_state_pending_ix_index"))
    realize a random batch of operations upon one db open FAILED [1]

Failures:

  test/unit/Cardano/Wallet/DB/SqliteCorruptionSpec.hs:160:9: 
  1) Cardano.Wallet.DB.SqliteCorruption, random operation chunks property when writing to/reading from file, realize a random batch of operations upon one db open
       uncaught exception: SqliteException
       SQLite3 returned ErrorError while attempting to perform prepare "CREATE INDEX IF NOT EXISTS pending_tx_wallet_id ON pending_tx (wallet_id)": no such table: main.pending_tx
       (after 1 test)

In detail we have in CI (the same reason as above listed ):

    Cardano.Wallet.DB.SqliteCorruption
      Check db opening/closing
        opening and closing of db works
      Check db reading/writing from/to file and cleaning
        create and list wallet works
        create and get meta works
        create and get private key
        put and read tx history
        put and read checkpoint
      random operation chunks property when writing to/reading from file
        realize a random batch of operations upon one db open FAILED [1]
    Cardano.Wallet.DB.Sqlite
      Sqlite Simple tests
        Wallet table
          create and list works FAILED [2]
          create and get meta works

I believe, this kind of testing is important as it is exposing some not so obvious issues that could come up with db connection and would be very difficult to understand later.

enableForeignKeys conn = do
stmt <- Sqlite.prepare conn "PRAGMA foreign_keys = ON;"
_ <- Sqlite.step stmt
Sqlite.finalize stmt
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is needed to be able to close and avoid SQLite3 returned ErrorBusy while attempting to perform close: unable to close due to unfinalized statements or unfinished backups

backend <- wrapConnection conn logFunc
pure (conn, backend)

createSqliteBackend1 :: Maybe FilePath -> LogFunc -> IO SqlBackend
Copy link
Contributor Author

Choose a reason for hiding this comment

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

need still to think how this can be married with createSqliteBackend

(conn, _backend) <- createSqliteBackend fp (dbLogs [LevelError])

let runQuery' cmd =
withMVar bigLock $ const $ runResourceT $ runNoLoggingT $ withSqlConn (createSqliteBackend1 fp)
Copy link
Contributor Author

@paweljakubas paweljakubas Jun 3, 2019

Choose a reason for hiding this comment

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

using withSqlConn is needed to be able to close and avoid SQLite3 returned ErrorBusy while attempting to perform close: unable to close due to unfinalized statements or unfinished backups OR we can somehow use close' there (remarks refers to file-based solution)

@paweljakubas
Copy link
Contributor Author

paweljakubas commented Jun 3, 2019

Strange:

newDBLayer Nothing

gives uncaught exception: SqliteException SQLite3 returned ErrorError while attempting to perform prepare "CREATE INDEX IF NOT EXISTS pending_tx_wallet_id ON pending_tx (wallet_id)": no such table: main.pending_tx

In contrast to

newDBLayer (Just "anything")

which works fine.
Logs point to addIndexes

@paweljakubas
Copy link
Contributor Author

paweljakubas commented Jun 3, 2019

@rvl @KtorZ I found the way to be green in this ^^^ hope you will examine this and better unify what I found - in the end it turn out that for in-memory mode there is old way (modulo finalize improvement in enableForeignKeys) and for file-based solution the one I proposed earlier.

@rvl rvl force-pushed the paweljakubas/154/add-db-read-write-to-file branch from 7b75243 to 87e8346 Compare June 4, 2019 06:39
@rvl
Copy link
Contributor

rvl commented Jun 4, 2019

@paweljakubas I look through the tests, poked some things, and tried to make the failing test pass.

However I don't think it's a realistic test to call Database.Sqlite.close on the connection used by DBLayer.

@rvl rvl force-pushed the paweljakubas/154/add-db-read-write-to-file branch from f4a6d02 to 7b75243 Compare June 5, 2019 10:46
@KtorZ KtorZ force-pushed the paweljakubas/154/add-db-read-write-to-file branch from 7b75243 to 71ebcd8 Compare June 5, 2019 15:15
@paweljakubas
Copy link
Contributor Author

@KtorZ I see once again my missing commits in the end, I recommend to close this PR in favor of #370 (this one is up-to-date, and by-and-large what was on Monday late evening)

@KtorZ
Copy link
Member

KtorZ commented Jun 6, 2019

Closing in favor of #370

@KtorZ KtorZ closed this Jun 6, 2019
@KtorZ KtorZ deleted the paweljakubas/154/add-db-read-write-to-file branch June 6, 2019 11:13
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.

3 participants