sqlite-easy 0.2.0.0 → 0.2.0.1
raw patch · 5 files changed
+47/−18 lines, 5 filesdep ~resource-pool
Dependency ranges changed: resource-pool
Files
- changelog.md +4/−0
- sqlite-easy.cabal +1/−1
- src/Database/Sqlite/Easy.hs +10/−10
- src/Database/Sqlite/Easy/Internal.hs +13/−6
- test/SqliteEasySpec.hs +19/−1
changelog.md view
@@ -1,3 +1,7 @@+### 0.2.0.1++- Fix: finalize prepared statements even when errors are encountered+ ### 0.2.0.0 * Rework the API
sqlite-easy.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.4 name: sqlite-easy-version: 0.2.0.0+version: 0.2.0.1 synopsis: A primitive yet easy to use sqlite library. description: A primitive yet easy to use sqlite library built using sqlite-direct, resource-pool and migrant. author: Gil Mizrahi
src/Database/Sqlite/Easy.hs view
@@ -58,12 +58,12 @@ {- $connecting The easiest way to run some statements on the database is using the-'withDb' function. 'withDb' uses continuation-passing style and expects-a connection string (such as a file with flags or @:memory:@,-see sqlite3 docs: <https://www.sqlite.org/c3ref/open.html>), and-a function that will take the database handle and do things with it.-It will open the connection to the database and call the function,-passing the database handle to it.+'withDb' function. 'withDb' expects a connection string+(such as a file with flags or @:memory:@, see sqlite3 docs:+<https://www.sqlite.org/c3ref/open.html>), and SQLite action(s),+such as queries and statements.+It will open the connection to the database and run the SQLite actions+on that database. === Example @@ -86,7 +86,7 @@ We can use the 'createSqlitePool' function to create a @Pool 'Database'@ and pass that around until you are ready to use the database. -We can use 'withPool' like we did with 'withDb' but passing a 'Database'+We can use 'withPool' like we did with 'withDb' but passing a @Pool Database@ instead of a 'ConnectionString'. === Example@@ -210,7 +210,7 @@ {- $suggestions -A suggestion for the architecture of your database interactions: use the handler pattern!+A suggestion for the architecture of your database interactions: use the handle pattern! === 1. Create a type for your API @@ -257,9 +257,9 @@ and so on. -=== 3. Use the handler from your application code+=== 3. Use the handle from your application code -When you start the application, run @mkDB@ and get a handler, and pass it around+When you start the application, run @mkDB@ and get a handle, and pass it around (or use @ReaderT@). When you need to run a database command, call a field from @DB@: > -- A page for a specific post
src/Database/Sqlite/Easy/Internal.hs view
@@ -61,16 +61,16 @@ run :: SQL -> SQLite [[SQLData]] run (SQL stmt) = do db <- getDB- liftIO (prepare db stmt >>= fetchAll)+ liftIO $ bracket (prepare db stmt) finalize fetchAll -- | Run a SQL statement with certain parameters on a database and fetch the results. runWith :: SQL -> [SQLData] -> SQLite [[SQLData]] runWith (SQL stmt) params = do db <- getDB liftIO $ do- preparedStmt <- prepare db stmt- bind preparedStmt params- fetchAll preparedStmt+ bracket (prepare db stmt) finalize $ \preparedStmt -> do+ bind preparedStmt params+ fetchAll preparedStmt -- | Run a statement and fetch all of the data. fetchAll :: Statement -> IO [[SQLData]]@@ -81,12 +81,19 @@ row <- columns stmt rows <- fetchAll stmt pure (row : rows)- Done -> do- finalize stmt+ Done -> pure [] -- * Transaction +-- | The type of actions to run on a SQLite database.+-- In essence, it is almost the same as @Database -> IO a@.+--+-- 'SQLite' actions can be created with the 'run' and 'runWith'+-- functions, and can be composed using the type class instances.+--+-- 'SQLite' actions can be run with the 'withDb', 'withDatabase',+-- and 'withPool' functions. newtype SQLite a = SQLite { unSQLite :: SQLiteStuff -> IO a
test/SqliteEasySpec.hs view
@@ -3,9 +3,10 @@ module SqliteEasySpec (spec) where +import Data.List (isInfixOf) import Test.Hspec import Database.Sqlite.Easy-import UnliftIO.Exception (try)+import UnliftIO.Exception (try, catch, displayException, SomeException(..)) spec :: Spec spec = do@@ -27,6 +28,23 @@ shouldBe result [[SQLInteger 1]]++ it "sql injection" $ do+ result <- withDb ":memory:" $ do+ [] <- run "create table students(name text not null)"+ [] <- runWith "insert into students values (?)" [SQLText "Robert'); DROP TABLE students;--"]+ run "select * from students"+ shouldBe+ result+ [[SQLText "Robert'); DROP TABLE students;--"]]++ it "finalize prepared statement on error" $ do+ shouldThrow+ ( withDb ":memory:" $ do+ [] <- run "create table test (id integer primary key autoincrement, val text unique)"+ run "insert into test (val) values ('hello'), ('hello')"+ )+ (\(SomeException e) -> "constraint failed: test.val" `isInfixOf` displayException e) describe "transactions" $ do it "transaction is rolled back in case of error" $ do