mirror of
https://github.com/unisonweb/unison.git
synced 2024-11-13 09:55:35 +03:00
Merge pull request #2668 from unisonweb/21-11-13-unison-sqlite
Extract unison-sqlite library from unison-codebase-sqlite
This commit is contained in:
commit
db67eac8c4
3
hie.yaml
3
hie.yaml
@ -27,6 +27,9 @@ cradle:
|
||||
- path: "lib/unison-prelude/src"
|
||||
component: "unison-prelude:lib"
|
||||
|
||||
- path: "lib/unison-sqlite/src"
|
||||
component: "unison-sqlite:lib"
|
||||
|
||||
- path: "lib/unison-util-relation/src"
|
||||
component: "unison-util-relation:lib"
|
||||
|
||||
|
@ -44,6 +44,7 @@ import Data.Text as X (Text)
|
||||
import qualified Data.Text as Text
|
||||
import Data.Text.Encoding as X (decodeUtf8, encodeUtf8)
|
||||
import Data.Traversable as X (for)
|
||||
import Data.Typeable as X (Typeable)
|
||||
import Data.Word as X
|
||||
import Debug.Trace as X
|
||||
import GHC.Generics as X (Generic, Generic1)
|
||||
|
3
lib/unison-sqlite/README.md
Normal file
3
lib/unison-sqlite/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# unison-sqlite
|
||||
|
||||
This package provides a interface to SQLite for Unison packages.
|
51
lib/unison-sqlite/package.yaml
Normal file
51
lib/unison-sqlite/package.yaml
Normal file
@ -0,0 +1,51 @@
|
||||
name: unison-sqlite
|
||||
github: unisonweb/unison
|
||||
copyright: Copyright (C) 2013-2021 Unison Computing, PBC and contributors
|
||||
|
||||
library:
|
||||
source-dirs: src
|
||||
other-modules:
|
||||
- Unison.Sqlite.DataVersion
|
||||
- Unison.Sqlite.Exception
|
||||
- Unison.Sqlite.JournalMode
|
||||
- Unison.Sqlite.Sql
|
||||
|
||||
dependencies:
|
||||
- base
|
||||
- direct-sqlite
|
||||
- exceptions
|
||||
- mtl
|
||||
- recover-rtti
|
||||
- sqlite-simple
|
||||
- text
|
||||
- transformers
|
||||
- unison-prelude
|
||||
- unliftio
|
||||
- unliftio-core
|
||||
|
||||
ghc-options:
|
||||
-Wall
|
||||
|
||||
default-extensions:
|
||||
- BlockArguments
|
||||
- ConstraintKinds
|
||||
- DeriveAnyClass
|
||||
- DeriveFunctor
|
||||
- DerivingStrategies
|
||||
- DerivingVia
|
||||
- DoAndIfThenElse
|
||||
- DuplicateRecordFields
|
||||
- FlexibleContexts
|
||||
- FlexibleInstances
|
||||
- GADTs
|
||||
- GeneralizedNewtypeDeriving
|
||||
- KindSignatures
|
||||
- LambdaCase
|
||||
- MultiParamTypeClasses
|
||||
- NamedFieldPuns
|
||||
- NumericUnderscores
|
||||
- OverloadedStrings
|
||||
- ScopedTypeVariables
|
||||
- TupleSections
|
||||
- TypeApplications
|
||||
- ViewPatterns
|
131
lib/unison-sqlite/src/Unison/Sqlite.hs
Normal file
131
lib/unison-sqlite/src/Unison/Sqlite.hs
Normal file
@ -0,0 +1,131 @@
|
||||
-- | The Unison monorepo interface to SQLite.
|
||||
--
|
||||
-- This module provides a high(-er) level interface to SQLite than the @sqlite-simple@ library, which it wraps. Code
|
||||
-- that interacts with SQLite in this monorepo should use this interface, rather than @sqlite-simple@ or @direct-sqlite@
|
||||
-- directly.
|
||||
--
|
||||
-- Three variants of the main query interface are provided:
|
||||
--
|
||||
-- * "Unison.Sqlite.Connection" provides an interface in @IO@, which takes the 'Connection' argument as an explicit
|
||||
-- argument.
|
||||
-- * "Unison.Sqlite.DB" provides a type class interface, which moves the 'Connection' to an implicit argument. This
|
||||
-- interface is also re-exported by this module, for convenient backwards compatibility with the existing queries.
|
||||
-- * "Unison.Sqlite.Transaction" provides a newer, yet-unused interface that executes queries in transactions, with
|
||||
-- automatic retries on @SQLITE_BUSY@ due to concurrent writers.
|
||||
module Unison.Sqlite
|
||||
( -- * Connection management
|
||||
Connection,
|
||||
withConnection,
|
||||
|
||||
-- * Type class query interface
|
||||
DB,
|
||||
runDB,
|
||||
|
||||
-- * Executing queries
|
||||
Sql (..),
|
||||
|
||||
-- ** Without results
|
||||
|
||||
-- *** With parameters
|
||||
execute,
|
||||
executeMany,
|
||||
|
||||
-- *** Without parameters
|
||||
execute_,
|
||||
|
||||
-- ** With results
|
||||
-- $query-naming-convention
|
||||
|
||||
-- *** With parameters
|
||||
queryListRow,
|
||||
queryListCol,
|
||||
queryMaybeRow,
|
||||
queryMaybeCol,
|
||||
queryOneRow,
|
||||
queryOneCol,
|
||||
|
||||
-- **** With checks
|
||||
queryListRowCheck,
|
||||
queryListColCheck,
|
||||
queryMaybeRowCheck,
|
||||
queryMaybeColCheck,
|
||||
queryOneRowCheck,
|
||||
queryOneColCheck,
|
||||
|
||||
-- *** Without parameters
|
||||
queryListRow_,
|
||||
queryListCol_,
|
||||
queryMaybeRow_,
|
||||
queryMaybeCol_,
|
||||
queryOneRow_,
|
||||
queryOneCol_,
|
||||
|
||||
-- **** With checks
|
||||
queryListRowCheck_,
|
||||
queryListColCheck_,
|
||||
queryMaybeRowCheck_,
|
||||
queryMaybeColCheck_,
|
||||
queryOneRowCheck_,
|
||||
queryOneColCheck_,
|
||||
|
||||
-- * Data version
|
||||
DataVersion (..),
|
||||
getDataVersion,
|
||||
|
||||
-- * Journal mode
|
||||
JournalMode (..),
|
||||
trySetJournalMode,
|
||||
|
||||
-- ** Low-level
|
||||
withSavepoint,
|
||||
withStatement,
|
||||
|
||||
-- * Exceptions
|
||||
SqliteException (..),
|
||||
SqliteExceptionReason,
|
||||
SomeSqliteExceptionReason (..),
|
||||
ExpectedAtMostOneRowException (..),
|
||||
ExpectedExactlyOneRowException (..),
|
||||
SetJournalModeException (..),
|
||||
)
|
||||
where
|
||||
|
||||
import Unison.Sqlite.Connection
|
||||
( Connection,
|
||||
ExpectedAtMostOneRowException (..),
|
||||
ExpectedExactlyOneRowException (..),
|
||||
withConnection,
|
||||
)
|
||||
import Unison.Sqlite.DB
|
||||
import Unison.Sqlite.DataVersion (DataVersion (..), getDataVersion)
|
||||
import Unison.Sqlite.Exception (SomeSqliteExceptionReason (..), SqliteException (..), SqliteExceptionReason)
|
||||
import Unison.Sqlite.JournalMode (JournalMode (..), SetJournalModeException (..), trySetJournalMode)
|
||||
import Unison.Sqlite.Sql (Sql (..))
|
||||
|
||||
-- $query-naming-convention
|
||||
--
|
||||
-- Queries that return results have many different variants.
|
||||
--
|
||||
-- Every function name begins with the string @__query__@.
|
||||
--
|
||||
-- 1. /Row count/. The caller may expect /exactly one/, /zero or one/, or /zero or more/ rows, in which case the
|
||||
-- function name includes the string @__List__@, @__Maybe__@, or @__One__@, respectively.
|
||||
-- Example: @query__List__Row@.
|
||||
--
|
||||
-- 2. /Row width/. The caller may expect the returned rows may contain /exactly one/ or /more than one/ column, in
|
||||
-- which case the function name includes the string @__Col__@ or @__Row__@, respectively.
|
||||
-- Example: @queryOne__Col__@.
|
||||
--
|
||||
-- 3. /Result checks/. The caller may want to perform additional validation on the returned rows, in which case the
|
||||
-- function name includes the string @__Check__@.
|
||||
-- Example: @queryMaybeCol__Check__@.
|
||||
--
|
||||
-- 4. /Parameter count/. The query may contain /zero/ or /one or more/ parameters. In the former case, the function
|
||||
-- name includes the string @__\___@.
|
||||
-- Example: @queryListRow__\___@.
|
||||
--
|
||||
-- All together, the full anatomy of a query function is:
|
||||
--
|
||||
-- @
|
||||
-- query(List|Maybe|One)(Row|Col)[Check][_]
|
||||
-- @
|
433
lib/unison-sqlite/src/Unison/Sqlite/Connection.hs
Normal file
433
lib/unison-sqlite/src/Unison/Sqlite/Connection.hs
Normal file
@ -0,0 +1,433 @@
|
||||
module Unison.Sqlite.Connection
|
||||
( -- * Connection management
|
||||
Connection (..),
|
||||
withConnection,
|
||||
|
||||
-- * Executing queries
|
||||
|
||||
-- ** Without results
|
||||
|
||||
-- *** With parameters
|
||||
execute,
|
||||
executeMany,
|
||||
|
||||
-- *** Without parameters
|
||||
execute_,
|
||||
|
||||
-- ** With results
|
||||
|
||||
-- *** With parameters
|
||||
queryListRow,
|
||||
queryListCol,
|
||||
queryMaybeRow,
|
||||
queryMaybeCol,
|
||||
queryOneRow,
|
||||
queryOneCol,
|
||||
|
||||
-- **** With checks
|
||||
queryListRowCheck,
|
||||
queryListColCheck,
|
||||
queryMaybeRowCheck,
|
||||
queryMaybeColCheck,
|
||||
queryOneRowCheck,
|
||||
queryOneColCheck,
|
||||
|
||||
-- *** Without parameters
|
||||
queryListRow_,
|
||||
queryListCol_,
|
||||
queryMaybeRow_,
|
||||
queryMaybeCol_,
|
||||
queryOneRow_,
|
||||
queryOneCol_,
|
||||
|
||||
-- **** With checks
|
||||
queryListRowCheck_,
|
||||
queryListColCheck_,
|
||||
queryMaybeRowCheck_,
|
||||
queryMaybeColCheck_,
|
||||
queryOneRowCheck_,
|
||||
queryOneColCheck_,
|
||||
|
||||
-- * Low-level operations
|
||||
withSavepoint,
|
||||
withStatement,
|
||||
|
||||
-- * Exceptions
|
||||
ExpectedAtMostOneRowException (..),
|
||||
ExpectedExactlyOneRowException (..),
|
||||
)
|
||||
where
|
||||
|
||||
import qualified Database.SQLite.Simple as Sqlite
|
||||
import qualified Database.SQLite.Simple.FromField as Sqlite
|
||||
import qualified Database.SQLite3.Direct as Sqlite (Database (..))
|
||||
import Debug.RecoverRTTI (anythingToString)
|
||||
import Unison.Prelude
|
||||
import Unison.Sqlite.Exception
|
||||
import Unison.Sqlite.Sql
|
||||
import UnliftIO (MonadUnliftIO)
|
||||
import UnliftIO.Exception
|
||||
|
||||
-- | A /non-thread safe/ connection to a SQLite database.
|
||||
data Connection = Connection
|
||||
{ name :: String,
|
||||
file :: FilePath,
|
||||
conn :: Sqlite.Connection
|
||||
}
|
||||
|
||||
instance Show Connection where
|
||||
show (Connection name file (Sqlite.Connection (Sqlite.Database conn))) =
|
||||
"Connection " ++ show name ++ " " ++ show file ++ " " ++ show conn
|
||||
|
||||
-- | Perform an action with a connection to a SQLite database.
|
||||
--
|
||||
-- Note: the connection is created with @PRAGMA foreign_keys = ON@ automatically, to work around the fact that SQLite
|
||||
-- does not automatically enforce foreign key integrity, because it elected to maintain backwards compatibility with
|
||||
-- code that was written before the foreign key integrity feature was implemented.
|
||||
withConnection ::
|
||||
MonadUnliftIO m =>
|
||||
-- | Connection name, for debugging.
|
||||
String ->
|
||||
-- | Path to SQLite database file.
|
||||
FilePath ->
|
||||
(Connection -> m a) ->
|
||||
m a
|
||||
withConnection name file =
|
||||
bracket (openConnection name file) closeConnection
|
||||
|
||||
-- Open a connection to a SQLite database.
|
||||
openConnection ::
|
||||
MonadIO m =>
|
||||
-- Connection name, for debugging.
|
||||
String ->
|
||||
-- Path to SQLite database file.
|
||||
FilePath ->
|
||||
m Connection
|
||||
openConnection name file = do
|
||||
conn0 <- liftIO (Sqlite.open file)
|
||||
let conn = Connection {conn = conn0, file, name}
|
||||
liftIO (execute_ conn "PRAGMA foreign_keys = ON")
|
||||
pure conn
|
||||
|
||||
-- Close a connection opened with 'openConnection'.
|
||||
closeConnection :: MonadIO m => Connection -> m ()
|
||||
closeConnection (Connection _ _ conn) =
|
||||
liftIO (Sqlite.close conn)
|
||||
|
||||
-- Without results, with parameters
|
||||
|
||||
execute :: Sqlite.ToRow a => Connection -> Sql -> a -> IO ()
|
||||
execute conn@(Connection _ _ conn0) s params =
|
||||
Sqlite.execute conn0 (coerce s) params `catch` \(exception :: Sqlite.SQLError) ->
|
||||
throwSqliteException
|
||||
SqliteExceptionInfo
|
||||
{ connection = conn,
|
||||
exception = SomeSqliteExceptionReason exception,
|
||||
params = Just params,
|
||||
sql = s
|
||||
}
|
||||
|
||||
executeMany :: Sqlite.ToRow a => Connection -> Sql -> [a] -> IO ()
|
||||
executeMany conn@(Connection _ _ conn0) s params =
|
||||
Sqlite.executeMany conn0 (coerce s) params `catch` \(exception :: Sqlite.SQLError) ->
|
||||
throwSqliteException
|
||||
SqliteExceptionInfo
|
||||
{ connection = conn,
|
||||
exception = SomeSqliteExceptionReason exception,
|
||||
params = Just params,
|
||||
sql = s
|
||||
}
|
||||
|
||||
-- Without results, without parameters
|
||||
|
||||
execute_ :: Connection -> Sql -> IO ()
|
||||
execute_ conn@(Connection _ _ conn0) s =
|
||||
Sqlite.execute_ conn0 (coerce s) `catch` \(exception :: Sqlite.SQLError) ->
|
||||
throwSqliteException
|
||||
SqliteExceptionInfo
|
||||
{ connection = conn,
|
||||
exception = SomeSqliteExceptionReason exception,
|
||||
params = Nothing,
|
||||
sql = s
|
||||
}
|
||||
|
||||
-- With results, with parameters, without checks
|
||||
|
||||
queryListRow :: (Sqlite.FromRow b, Sqlite.ToRow a) => Connection -> Sql -> a -> IO [b]
|
||||
queryListRow conn@(Connection _ _ conn0) s params =
|
||||
Sqlite.query conn0 (coerce s) params `catch` \(exception :: Sqlite.SQLError) ->
|
||||
throwSqliteException
|
||||
SqliteExceptionInfo
|
||||
{ connection = conn,
|
||||
exception = SomeSqliteExceptionReason exception,
|
||||
params = Just params,
|
||||
sql = s
|
||||
}
|
||||
|
||||
queryListCol :: forall a b. (Sqlite.FromField b, Sqlite.ToRow a) => Connection -> Sql -> a -> IO [b]
|
||||
queryListCol conn s params =
|
||||
coerce @(IO [Sqlite.Only b]) @(IO [b]) (queryListRow conn s params)
|
||||
|
||||
queryMaybeRow :: (Sqlite.ToRow a, Sqlite.FromRow b) => Connection -> Sql -> a -> IO (Maybe b)
|
||||
queryMaybeRow conn s params =
|
||||
queryListRowCheck conn s params \case
|
||||
[] -> Right Nothing
|
||||
[x] -> Right (Just x)
|
||||
xs -> Left (ExpectedAtMostOneRowException (anythingToString xs))
|
||||
|
||||
queryMaybeCol :: forall a b. (Sqlite.ToRow a, Sqlite.FromField b) => Connection -> Sql -> a -> IO (Maybe b)
|
||||
queryMaybeCol conn s params =
|
||||
coerce @(IO (Maybe (Sqlite.Only b))) @(IO (Maybe b)) (queryMaybeRow conn s params)
|
||||
|
||||
queryOneRow :: (Sqlite.FromRow b, Sqlite.ToRow a) => Connection -> Sql -> a -> IO b
|
||||
queryOneRow conn s params =
|
||||
queryListRowCheck conn s params \case
|
||||
[x] -> Right x
|
||||
xs -> Left (ExpectedExactlyOneRowException (anythingToString xs))
|
||||
|
||||
queryOneCol :: forall a b. (Sqlite.FromField b, Sqlite.ToRow a) => Connection -> Sql -> a -> IO b
|
||||
queryOneCol conn s params = do
|
||||
coerce @(IO (Sqlite.Only b)) @(IO b) (queryOneRow conn s params)
|
||||
|
||||
-- With results, with parameters, with checks
|
||||
|
||||
queryListRowCheck ::
|
||||
(Sqlite.FromRow b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Connection ->
|
||||
Sql ->
|
||||
a ->
|
||||
([b] -> Either e r) ->
|
||||
IO r
|
||||
queryListRowCheck conn s params check =
|
||||
gqueryListCheck conn s params (mapLeft SomeSqliteExceptionReason . check)
|
||||
|
||||
gqueryListCheck ::
|
||||
(Sqlite.FromRow b, Sqlite.ToRow a) =>
|
||||
Connection ->
|
||||
Sql ->
|
||||
a ->
|
||||
([b] -> Either SomeSqliteExceptionReason r) ->
|
||||
IO r
|
||||
gqueryListCheck conn s params check = do
|
||||
xs <- queryListRow conn s params
|
||||
case check xs of
|
||||
Left exception ->
|
||||
throwSqliteException
|
||||
SqliteExceptionInfo
|
||||
{ connection = conn,
|
||||
exception,
|
||||
params = Just params,
|
||||
sql = s
|
||||
}
|
||||
Right result -> pure result
|
||||
|
||||
queryListColCheck ::
|
||||
forall a b e r.
|
||||
(Sqlite.FromField b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Connection ->
|
||||
Sql ->
|
||||
a ->
|
||||
([b] -> Either e r) ->
|
||||
IO r
|
||||
queryListColCheck conn s params check =
|
||||
queryListRowCheck conn s params (coerce @([b] -> Either e r) @([Sqlite.Only b] -> Either e r) check)
|
||||
|
||||
queryMaybeRowCheck ::
|
||||
(Sqlite.FromRow b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Connection ->
|
||||
Sql ->
|
||||
a ->
|
||||
(Maybe b -> Either e r) ->
|
||||
IO r
|
||||
queryMaybeRowCheck conn s params check =
|
||||
gqueryListCheck conn s params \case
|
||||
[] -> mapLeft SomeSqliteExceptionReason (check Nothing)
|
||||
[x] -> mapLeft SomeSqliteExceptionReason (check (Just x))
|
||||
xs -> Left (SomeSqliteExceptionReason (ExpectedAtMostOneRowException (anythingToString xs)))
|
||||
|
||||
queryMaybeColCheck ::
|
||||
forall a b e r.
|
||||
(Sqlite.FromField b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Connection ->
|
||||
Sql ->
|
||||
a ->
|
||||
(Maybe b -> Either e r) ->
|
||||
IO r
|
||||
queryMaybeColCheck conn s params check =
|
||||
queryMaybeRowCheck conn s params (coerce @(Maybe b -> Either e r) @(Maybe (Sqlite.Only b) -> Either e r) check)
|
||||
|
||||
queryOneRowCheck ::
|
||||
(Sqlite.FromRow b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Connection ->
|
||||
Sql ->
|
||||
a ->
|
||||
(b -> Either e r) ->
|
||||
IO r
|
||||
queryOneRowCheck conn s params check =
|
||||
gqueryListCheck conn s params \case
|
||||
[x] -> mapLeft SomeSqliteExceptionReason (check x)
|
||||
xs -> Left (SomeSqliteExceptionReason (ExpectedExactlyOneRowException (anythingToString xs)))
|
||||
|
||||
queryOneColCheck ::
|
||||
forall a b e r.
|
||||
(Sqlite.FromField b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Connection ->
|
||||
Sql ->
|
||||
a ->
|
||||
(b -> Either e r) ->
|
||||
IO r
|
||||
queryOneColCheck conn s params check =
|
||||
queryOneRowCheck conn s params (coerce @(b -> Either e r) @(Sqlite.Only b -> Either e r) check)
|
||||
|
||||
-- With results, without parameters, without checks
|
||||
|
||||
queryListRow_ :: Sqlite.FromRow a => Connection -> Sql -> IO [a]
|
||||
queryListRow_ conn@(Connection _ _ conn0) s =
|
||||
Sqlite.query_ conn0 (coerce s) `catch` \(exception :: Sqlite.SQLError) ->
|
||||
throwSqliteException
|
||||
SqliteExceptionInfo
|
||||
{ connection = conn,
|
||||
exception = SomeSqliteExceptionReason exception,
|
||||
params = Nothing,
|
||||
sql = s
|
||||
}
|
||||
|
||||
queryListCol_ :: forall a. Sqlite.FromField a => Connection -> Sql -> IO [a]
|
||||
queryListCol_ conn s =
|
||||
coerce @(IO [Sqlite.Only a]) @(IO [a]) (queryListRow_ conn s)
|
||||
|
||||
queryMaybeRow_ :: Sqlite.FromRow a => Connection -> Sql -> IO (Maybe a)
|
||||
queryMaybeRow_ conn s =
|
||||
queryListRowCheck_ conn s \case
|
||||
[] -> Right Nothing
|
||||
[x] -> Right (Just x)
|
||||
xs -> Left (SomeSqliteExceptionReason (ExpectedAtMostOneRowException (anythingToString xs)))
|
||||
|
||||
queryMaybeCol_ :: forall a. Sqlite.FromField a => Connection -> Sql -> IO (Maybe a)
|
||||
queryMaybeCol_ conn s =
|
||||
coerce @(IO (Maybe (Sqlite.Only a))) @(IO (Maybe a)) (queryMaybeRow_ conn s)
|
||||
|
||||
queryOneRow_ :: Sqlite.FromRow a => Connection -> Sql -> IO a
|
||||
queryOneRow_ conn s =
|
||||
queryListRowCheck_ conn s \case
|
||||
[x] -> Right x
|
||||
xs -> Left (SomeSqliteExceptionReason (ExpectedExactlyOneRowException (anythingToString xs)))
|
||||
|
||||
queryOneCol_ :: forall a. Sqlite.FromField a => Connection -> Sql -> IO a
|
||||
queryOneCol_ conn s =
|
||||
coerce @(IO (Sqlite.Only a)) @(IO a) (queryOneRow_ conn s)
|
||||
|
||||
-- With results, without parameters, with checks
|
||||
|
||||
queryListRowCheck_ :: (Sqlite.FromRow a, SqliteExceptionReason e) => Connection -> Sql -> ([a] -> Either e r) -> IO r
|
||||
queryListRowCheck_ conn s check =
|
||||
gqueryListCheck_ conn s (mapLeft SomeSqliteExceptionReason . check)
|
||||
|
||||
gqueryListCheck_ :: Sqlite.FromRow a => Connection -> Sql -> ([a] -> Either SomeSqliteExceptionReason r) -> IO r
|
||||
gqueryListCheck_ conn s check = do
|
||||
xs <- queryListRow_ conn s
|
||||
case check xs of
|
||||
Left exception ->
|
||||
throwSqliteException
|
||||
SqliteExceptionInfo
|
||||
{ connection = conn,
|
||||
exception,
|
||||
params = Nothing,
|
||||
sql = s
|
||||
}
|
||||
Right result -> pure result
|
||||
|
||||
queryListColCheck_ ::
|
||||
forall a e r.
|
||||
(Sqlite.FromField a, SqliteExceptionReason e) =>
|
||||
Connection ->
|
||||
Sql ->
|
||||
([a] -> Either e r) ->
|
||||
IO r
|
||||
queryListColCheck_ conn s check =
|
||||
queryListRowCheck_ conn s (coerce @([a] -> Either e r) @([Sqlite.Only a] -> Either e r) check)
|
||||
|
||||
queryMaybeRowCheck_ :: (Sqlite.FromRow a, SqliteExceptionReason e) => Connection -> Sql -> (Maybe a -> Either e r) -> IO r
|
||||
queryMaybeRowCheck_ conn s check =
|
||||
gqueryListCheck_ conn s \case
|
||||
[] -> mapLeft SomeSqliteExceptionReason (check Nothing)
|
||||
[x] -> mapLeft SomeSqliteExceptionReason (check (Just x))
|
||||
xs -> Left (SomeSqliteExceptionReason (ExpectedAtMostOneRowException (anythingToString xs)))
|
||||
|
||||
queryMaybeColCheck_ ::
|
||||
forall a e r.
|
||||
(Sqlite.FromField a, SqliteExceptionReason e) =>
|
||||
Connection ->
|
||||
Sql ->
|
||||
(Maybe a -> Either e r) ->
|
||||
IO r
|
||||
queryMaybeColCheck_ conn s check =
|
||||
queryMaybeRowCheck_ conn s (coerce @(Maybe a -> Either e r) @(Maybe (Sqlite.Only a) -> Either e r) check)
|
||||
|
||||
queryOneRowCheck_ :: (Sqlite.FromRow a, SqliteExceptionReason e) => Connection -> Sql -> (a -> Either e r) -> IO r
|
||||
queryOneRowCheck_ conn s check =
|
||||
gqueryListCheck_ conn s \case
|
||||
[x] -> mapLeft SomeSqliteExceptionReason (check x)
|
||||
xs -> Left (SomeSqliteExceptionReason (ExpectedExactlyOneRowException (anythingToString xs)))
|
||||
|
||||
queryOneColCheck_ ::
|
||||
forall a e r.
|
||||
(Sqlite.FromField a, SqliteExceptionReason e) =>
|
||||
Connection ->
|
||||
Sql ->
|
||||
(a -> Either e r) ->
|
||||
IO r
|
||||
queryOneColCheck_ conn s check =
|
||||
queryOneRowCheck_ conn s (coerce @(a -> Either e r) @(Sqlite.Only a -> Either e r) check)
|
||||
|
||||
-- Low-level
|
||||
|
||||
-- | Perform an action within a named savepoint. The action is provided a rollback action.
|
||||
withSavepoint :: Connection -> Text -> (IO () -> IO a) -> IO a
|
||||
withSavepoint conn name action = do
|
||||
uninterruptibleMask \restore -> do
|
||||
execute_ conn (Sql ("SAVEPOINT " <> name))
|
||||
result <-
|
||||
restore (action rollback) `onException` do
|
||||
rollback
|
||||
release
|
||||
release
|
||||
pure result
|
||||
where
|
||||
rollback = execute_ conn (Sql ("ROLLBACK TO " <> name))
|
||||
release = execute_ conn (Sql ("RELEASE " <> name))
|
||||
|
||||
withStatement :: (Sqlite.FromRow a, Sqlite.ToRow b) => Connection -> Sql -> b -> (IO (Maybe a) -> IO c) -> IO c
|
||||
withStatement conn@(Connection _ _ conn0) s params callback =
|
||||
thing `catch` \(exception :: Sqlite.SQLError) ->
|
||||
throwSqliteException
|
||||
SqliteExceptionInfo
|
||||
{ connection = conn,
|
||||
exception = SomeSqliteExceptionReason exception,
|
||||
params = Just params,
|
||||
sql = s
|
||||
}
|
||||
where
|
||||
thing =
|
||||
bracket (Sqlite.openStatement conn0 (coerce s)) Sqlite.closeStatement \statement -> do
|
||||
Sqlite.bind statement params
|
||||
callback (Sqlite.nextRow statement)
|
||||
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
-- Exceptions
|
||||
|
||||
-- | A query was expected to return exactly one row, but it did not. The exception carries a string representation of
|
||||
-- the rows that were actually returned.
|
||||
newtype ExpectedExactlyOneRowException = ExpectedExactlyOneRowException
|
||||
{ rows :: String
|
||||
}
|
||||
deriving stock (Show)
|
||||
deriving anyclass (SqliteExceptionReason)
|
||||
|
||||
-- | A query was expected to return exactly one row, but it did not. The exception carries a string representation of
|
||||
-- the rows that were actually returned.
|
||||
newtype ExpectedAtMostOneRowException = ExpectedAtMostOneRowException
|
||||
{ rows :: String
|
||||
}
|
||||
deriving stock (Show)
|
||||
deriving anyclass (SqliteExceptionReason)
|
273
lib/unison-sqlite/src/Unison/Sqlite/DB.hs
Normal file
273
lib/unison-sqlite/src/Unison/Sqlite/DB.hs
Normal file
@ -0,0 +1,273 @@
|
||||
-- | A type class interface to SQLite.
|
||||
module Unison.Sqlite.DB
|
||||
( -- * Type-class
|
||||
DB,
|
||||
runDB,
|
||||
runTransaction,
|
||||
|
||||
-- * Executing queries
|
||||
|
||||
-- ** Without results
|
||||
|
||||
-- *** With parameters
|
||||
execute,
|
||||
executeMany,
|
||||
|
||||
-- *** Without parameters
|
||||
execute_,
|
||||
|
||||
-- ** With results
|
||||
|
||||
-- *** With parameters
|
||||
queryListRow,
|
||||
queryListCol,
|
||||
queryMaybeRow,
|
||||
queryMaybeCol,
|
||||
queryOneRow,
|
||||
queryOneCol,
|
||||
|
||||
-- **** With checks
|
||||
queryListRowCheck,
|
||||
queryListColCheck,
|
||||
queryMaybeRowCheck,
|
||||
queryMaybeColCheck,
|
||||
queryOneRowCheck,
|
||||
queryOneColCheck,
|
||||
|
||||
-- *** Without parameters
|
||||
queryListRow_,
|
||||
queryListCol_,
|
||||
queryMaybeRow_,
|
||||
queryMaybeCol_,
|
||||
queryOneRow_,
|
||||
queryOneCol_,
|
||||
|
||||
-- **** With checks
|
||||
queryListRowCheck_,
|
||||
queryListColCheck_,
|
||||
queryMaybeRowCheck_,
|
||||
queryMaybeColCheck_,
|
||||
queryOneRowCheck_,
|
||||
queryOneColCheck_,
|
||||
|
||||
-- * Low-level operations
|
||||
withSavepoint,
|
||||
withStatement,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad.IO.Unlift (MonadUnliftIO, withRunInIO)
|
||||
import Control.Monad.Reader (MonadReader, ReaderT, ask, runReaderT)
|
||||
import qualified Database.SQLite.Simple as Sqlite
|
||||
import qualified Database.SQLite.Simple.FromField as Sqlite
|
||||
import Unison.Prelude
|
||||
import Unison.Sqlite.Connection (Connection)
|
||||
import qualified Unison.Sqlite.Connection as Connection
|
||||
import Unison.Sqlite.Exception (SqliteExceptionReason)
|
||||
import Unison.Sqlite.Sql (Sql (..))
|
||||
import Unison.Sqlite.Transaction (Transaction)
|
||||
import qualified Unison.Sqlite.Transaction as Transaction
|
||||
|
||||
type DB m =
|
||||
(MonadIO m, MonadReader Connection m)
|
||||
|
||||
runDB :: MonadIO m => Connection -> ReaderT Connection m a -> m a
|
||||
runDB conn action =
|
||||
runReaderT action conn
|
||||
|
||||
runTransaction :: DB m => Transaction a -> m a
|
||||
runTransaction transaction = do
|
||||
conn <- ask
|
||||
Transaction.runTransaction conn transaction
|
||||
|
||||
-- Without results, with parameters
|
||||
|
||||
execute :: (DB m, Sqlite.ToRow a) => Sql -> a -> m ()
|
||||
execute s params = do
|
||||
conn <- ask
|
||||
liftIO (Connection.execute conn s params)
|
||||
|
||||
executeMany :: (DB m, Sqlite.ToRow a) => Sql -> [a] -> m ()
|
||||
executeMany s params = do
|
||||
conn <- ask
|
||||
liftIO (Connection.executeMany conn s params)
|
||||
|
||||
-- Without results, without parameters
|
||||
|
||||
execute_ :: DB m => Sql -> m ()
|
||||
execute_ s = do
|
||||
conn <- ask
|
||||
liftIO (Connection.execute_ conn s)
|
||||
|
||||
-- With results, with parameters, without checks
|
||||
|
||||
queryListRow :: (DB m, Sqlite.FromRow a, Sqlite.ToRow b) => Sql -> b -> m [a]
|
||||
queryListRow s params = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryListRow conn s params)
|
||||
|
||||
queryListCol :: (DB m, Sqlite.FromField a, Sqlite.ToRow b) => Sql -> b -> m [a]
|
||||
queryListCol s params = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryListCol conn s params)
|
||||
|
||||
queryMaybeRow :: (DB m, Sqlite.FromRow a, Sqlite.ToRow b) => Sql -> b -> m (Maybe a)
|
||||
queryMaybeRow s params = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryMaybeRow conn s params)
|
||||
|
||||
queryMaybeCol :: (DB m, Sqlite.FromField a, Sqlite.ToRow b) => Sql -> b -> m (Maybe a)
|
||||
queryMaybeCol s params = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryMaybeCol conn s params)
|
||||
|
||||
queryOneRow :: (DB m, Sqlite.FromRow b, Sqlite.ToRow a) => Sql -> a -> m b
|
||||
queryOneRow s params = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryOneRow conn s params)
|
||||
|
||||
queryOneCol :: (DB m, Sqlite.FromField b, Sqlite.ToRow a) => Sql -> a -> m b
|
||||
queryOneCol s params = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryOneCol conn s params)
|
||||
|
||||
-- With results, with parameters, with checks
|
||||
|
||||
queryListRowCheck ::
|
||||
(DB m, Sqlite.FromRow b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Sql ->
|
||||
a ->
|
||||
([b] -> Either e r) ->
|
||||
m r
|
||||
queryListRowCheck s params check = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryListRowCheck conn s params check)
|
||||
|
||||
queryListColCheck ::
|
||||
(DB m, Sqlite.FromField b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Sql ->
|
||||
a ->
|
||||
([b] -> Either e r) ->
|
||||
m r
|
||||
queryListColCheck s params check = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryListColCheck conn s params check)
|
||||
|
||||
queryMaybeRowCheck ::
|
||||
(DB m, Sqlite.FromRow b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Sql ->
|
||||
a ->
|
||||
(Maybe b -> Either e r) ->
|
||||
m r
|
||||
queryMaybeRowCheck s params check = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryMaybeRowCheck conn s params check)
|
||||
|
||||
queryMaybeColCheck ::
|
||||
(DB m, Sqlite.FromField b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Sql ->
|
||||
a ->
|
||||
(Maybe b -> Either e r) ->
|
||||
m r
|
||||
queryMaybeColCheck s params check = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryMaybeColCheck conn s params check)
|
||||
|
||||
queryOneRowCheck ::
|
||||
(DB m, Sqlite.FromRow b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Sql ->
|
||||
a ->
|
||||
(b -> Either e r) ->
|
||||
m r
|
||||
queryOneRowCheck s params check = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryOneRowCheck conn s params check)
|
||||
|
||||
queryOneColCheck ::
|
||||
(DB m, Sqlite.FromField b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Sql ->
|
||||
a ->
|
||||
(b -> Either e r) ->
|
||||
m r
|
||||
queryOneColCheck s params check = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryOneColCheck conn s params check)
|
||||
|
||||
-- With results, without parameters, without checks
|
||||
|
||||
queryListRow_ :: (DB m, Sqlite.FromRow a) => Sql -> m [a]
|
||||
queryListRow_ s = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryListRow_ conn s)
|
||||
|
||||
queryListCol_ :: (DB m, Sqlite.FromField a) => Sql -> m [a]
|
||||
queryListCol_ s = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryListCol_ conn s)
|
||||
|
||||
queryMaybeRow_ :: (DB m, Sqlite.FromRow a) => Sql -> m (Maybe a)
|
||||
queryMaybeRow_ s = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryMaybeRow_ conn s)
|
||||
|
||||
queryMaybeCol_ :: (DB m, Sqlite.FromField a) => Sql -> m (Maybe a)
|
||||
queryMaybeCol_ s = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryMaybeCol_ conn s)
|
||||
|
||||
queryOneRow_ :: (DB m, Sqlite.FromRow a) => Sql -> m a
|
||||
queryOneRow_ s = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryOneRow_ conn s)
|
||||
|
||||
queryOneCol_ :: (DB m, Sqlite.FromField a) => Sql -> m a
|
||||
queryOneCol_ s = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryOneCol_ conn s)
|
||||
|
||||
-- With results, without parameters, with checks
|
||||
|
||||
queryListRowCheck_ :: (DB m, Sqlite.FromRow a, SqliteExceptionReason e) => Sql -> ([a] -> Either e r) -> m r
|
||||
queryListRowCheck_ s check = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryListRowCheck_ conn s check)
|
||||
|
||||
queryListColCheck_ :: (DB m, Sqlite.FromField a, SqliteExceptionReason e) => Sql -> ([a] -> Either e r) -> m r
|
||||
queryListColCheck_ s check = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryListColCheck_ conn s check)
|
||||
|
||||
queryMaybeRowCheck_ :: (DB m, Sqlite.FromRow a, SqliteExceptionReason e) => Sql -> (Maybe a -> Either e r) -> m r
|
||||
queryMaybeRowCheck_ s check = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryMaybeRowCheck_ conn s check)
|
||||
|
||||
queryMaybeColCheck_ :: (DB m, Sqlite.FromField a, SqliteExceptionReason e) => Sql -> (Maybe a -> Either e r) -> m r
|
||||
queryMaybeColCheck_ s check = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryMaybeColCheck_ conn s check)
|
||||
|
||||
queryOneRowCheck_ :: (DB m, Sqlite.FromRow a, SqliteExceptionReason e) => Sql -> (a -> Either e r) -> m r
|
||||
queryOneRowCheck_ s check = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryOneRowCheck_ conn s check)
|
||||
|
||||
queryOneColCheck_ :: (DB m, Sqlite.FromField a, SqliteExceptionReason e) => Sql -> (a -> Either e r) -> m r
|
||||
queryOneColCheck_ s check = do
|
||||
conn <- ask
|
||||
liftIO (Connection.queryOneColCheck_ conn s check)
|
||||
|
||||
-- Low-level
|
||||
|
||||
-- | Perform an action within a named savepoint. The action is provided a rollback action.
|
||||
withSavepoint :: (DB m, MonadUnliftIO m) => Text -> (m () -> m a) -> m a
|
||||
withSavepoint name action = do
|
||||
conn <- ask
|
||||
withRunInIO \unlift ->
|
||||
liftIO (Connection.withSavepoint conn name (unlift . action . liftIO))
|
||||
|
||||
withStatement :: (DB m, MonadUnliftIO m, Sqlite.FromRow a, Sqlite.ToRow b) => Sql -> b -> (m (Maybe a) -> m c) -> m c
|
||||
withStatement s params callback = do
|
||||
conn <- ask
|
||||
withRunInIO \unlift ->
|
||||
Connection.withStatement conn s params (unlift . callback . liftIO)
|
17
lib/unison-sqlite/src/Unison/Sqlite/DataVersion.hs
Normal file
17
lib/unison-sqlite/src/Unison/Sqlite/DataVersion.hs
Normal file
@ -0,0 +1,17 @@
|
||||
module Unison.Sqlite.DataVersion
|
||||
( DataVersion (..),
|
||||
getDataVersion,
|
||||
)
|
||||
where
|
||||
|
||||
import Unison.Prelude
|
||||
import Unison.Sqlite.Transaction
|
||||
|
||||
newtype DataVersion
|
||||
= DataVersion Int64
|
||||
deriving stock (Eq)
|
||||
deriving newtype (Show)
|
||||
|
||||
getDataVersion :: Transaction DataVersion
|
||||
getDataVersion =
|
||||
coerce @(Transaction Int64) (queryOneCol_ "PRAGMA data_version")
|
75
lib/unison-sqlite/src/Unison/Sqlite/Exception.hs
Normal file
75
lib/unison-sqlite/src/Unison/Sqlite/Exception.hs
Normal file
@ -0,0 +1,75 @@
|
||||
-- | Sqlite exception utils
|
||||
module Unison.Sqlite.Exception
|
||||
( SqliteException (..),
|
||||
SqliteExceptionReason,
|
||||
SomeSqliteExceptionReason (..),
|
||||
SqliteExceptionInfo (..),
|
||||
throwSqliteException,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Concurrent (ThreadId, myThreadId)
|
||||
import qualified Database.SQLite.Simple as Sqlite
|
||||
import Debug.RecoverRTTI (anythingToString)
|
||||
import Unison.Prelude
|
||||
import Unison.Sqlite.Sql
|
||||
import UnliftIO.Exception
|
||||
|
||||
data SqliteExceptionInfo params connection = SqliteExceptionInfo
|
||||
{ sql :: Sql,
|
||||
params :: Maybe params,
|
||||
exception :: SomeSqliteExceptionReason,
|
||||
connection :: connection
|
||||
}
|
||||
|
||||
throwSqliteException :: Show connection => SqliteExceptionInfo params connection -> IO a
|
||||
throwSqliteException SqliteExceptionInfo {connection, exception, params, sql} = do
|
||||
threadId <- myThreadId
|
||||
throwIO
|
||||
SqliteException
|
||||
{ sql,
|
||||
params = maybe "" anythingToString params,
|
||||
exception,
|
||||
connection = show connection,
|
||||
threadId
|
||||
}
|
||||
|
||||
-- | A type that is intended to be used as additional context for a sqlite-related exception.
|
||||
class (Show e, Typeable e) => SqliteExceptionReason e
|
||||
|
||||
instance SqliteExceptionReason Sqlite.SQLError
|
||||
|
||||
data SomeSqliteExceptionReason
|
||||
= forall e. SqliteExceptionReason e => SomeSqliteExceptionReason e
|
||||
deriving anyclass (SqliteExceptionReason)
|
||||
|
||||
instance Show SomeSqliteExceptionReason where
|
||||
show (SomeSqliteExceptionReason x) = show x
|
||||
|
||||
-- | A @SqliteException@ represents an exception paired with some context that resulted in the exception.
|
||||
--
|
||||
-- A @SqliteException@ may result from a number of different conditions:
|
||||
--
|
||||
-- * The underlying sqlite library threw an exception, as when establishing a connection to a non-existent database.
|
||||
-- * A postcondition violation of a function like 'queryMaybe', which asserts that the resulting relation will have
|
||||
-- certain number of rows,
|
||||
-- * A postcondition violation of a function like 'queryListCheck', which takes a user-defined check as an argument.
|
||||
--
|
||||
-- A @SqliteException@ should not be inspected or used for control flow when run in a trusted environment, where the
|
||||
-- database can be assumed to be uncorrupt. Rather, wherever possible, the user of this library should write code that
|
||||
-- is guaranteed not to throw exceptions, by checking the necessary preconditions first. If that is not possible, it
|
||||
-- should be considered a bug in this library.
|
||||
--
|
||||
-- When actions are run on an untrusted codebase, e.g. one downloaded from a remote server, it is sufficient to catch
|
||||
-- just one exception type, @SqliteException@.
|
||||
data SqliteException = SqliteException
|
||||
{ sql :: Sql,
|
||||
params :: String,
|
||||
-- | The inner exception. It is intentionally not 'SomeException', so that calling code cannot accidentally
|
||||
-- 'throwIO' domain-specific exception types, but must instead use a @*Check@ query variant.
|
||||
exception :: SomeSqliteExceptionReason,
|
||||
connection :: String,
|
||||
threadId :: ThreadId
|
||||
}
|
||||
deriving stock (Show)
|
||||
deriving anyclass (Exception)
|
68
lib/unison-sqlite/src/Unison/Sqlite/JournalMode.hs
Normal file
68
lib/unison-sqlite/src/Unison/Sqlite/JournalMode.hs
Normal file
@ -0,0 +1,68 @@
|
||||
module Unison.Sqlite.JournalMode
|
||||
( JournalMode (..),
|
||||
trySetJournalMode,
|
||||
SetJournalModeException (..),
|
||||
)
|
||||
where
|
||||
|
||||
import qualified Data.Text as Text
|
||||
import qualified Database.SQLite.Simple as Sqlite
|
||||
import Unison.Prelude
|
||||
import Unison.Sqlite.Exception (SqliteExceptionReason)
|
||||
import Unison.Sqlite.Sql
|
||||
import Unison.Sqlite.Transaction
|
||||
|
||||
-- | https://www.sqlite.org/pragma.html#pragma_journal_mode
|
||||
data JournalMode
|
||||
= JournalMode'DELETE
|
||||
| JournalMode'TRUNCATE
|
||||
| JournalMode'PERSIST
|
||||
| JournalMode'MEMORY
|
||||
| JournalMode'WAL
|
||||
| JournalMode'OFF
|
||||
deriving stock (Eq, Show)
|
||||
|
||||
journalModeFromText :: Text -> Maybe JournalMode
|
||||
journalModeFromText = \case
|
||||
"DELETE" -> Just JournalMode'DELETE
|
||||
"TRUNCATE" -> Just JournalMode'TRUNCATE
|
||||
"PERSIST" -> Just JournalMode'PERSIST
|
||||
"MEMORY" -> Just JournalMode'MEMORY
|
||||
"WAL" -> Just JournalMode'WAL
|
||||
"OFF" -> Just JournalMode'OFF
|
||||
_ -> Nothing
|
||||
|
||||
unsafeJournalModeFromText :: HasCallStack => Text -> JournalMode
|
||||
unsafeJournalModeFromText s =
|
||||
fromMaybe (error ("Unknown journal mode: " ++ Text.unpack s)) (journalModeFromText s)
|
||||
|
||||
journalModeToText :: JournalMode -> Text
|
||||
journalModeToText = \case
|
||||
JournalMode'DELETE -> "DELETE"
|
||||
JournalMode'TRUNCATE -> "TRUNCATE"
|
||||
JournalMode'PERSIST -> "PERSIST"
|
||||
JournalMode'MEMORY -> "MEMORY"
|
||||
JournalMode'WAL -> "WAL"
|
||||
JournalMode'OFF -> "OFF"
|
||||
|
||||
trySetJournalMode :: JournalMode -> Transaction ()
|
||||
trySetJournalMode mode0 = do
|
||||
queryOneRowCheck_
|
||||
(Sql ("PRAGMA journal_mode = " <> journalModeToText mode0))
|
||||
\(Sqlite.Only mode1s) ->
|
||||
let mode1 = unsafeJournalModeFromText mode1s
|
||||
in if mode0 /= mode1
|
||||
then
|
||||
Left
|
||||
SetJournalModeException
|
||||
{ currentJournalMode = mode1,
|
||||
couldntSetTo = mode0
|
||||
}
|
||||
else Right ()
|
||||
|
||||
data SetJournalModeException = SetJournalModeException
|
||||
{ currentJournalMode :: JournalMode,
|
||||
couldntSetTo :: JournalMode
|
||||
}
|
||||
deriving stock (Show)
|
||||
deriving anyclass (SqliteExceptionReason)
|
11
lib/unison-sqlite/src/Unison/Sqlite/Sql.hs
Normal file
11
lib/unison-sqlite/src/Unison/Sqlite/Sql.hs
Normal file
@ -0,0 +1,11 @@
|
||||
module Unison.Sqlite.Sql
|
||||
( Sql (..),
|
||||
)
|
||||
where
|
||||
|
||||
import Unison.Prelude
|
||||
|
||||
-- | A SQL snippet.
|
||||
newtype Sql
|
||||
= Sql Text
|
||||
deriving newtype (IsString, Show)
|
226
lib/unison-sqlite/src/Unison/Sqlite/Transaction.hs
Normal file
226
lib/unison-sqlite/src/Unison/Sqlite/Transaction.hs
Normal file
@ -0,0 +1,226 @@
|
||||
module Unison.Sqlite.Transaction
|
||||
( -- * Transaction management
|
||||
Transaction,
|
||||
runTransaction,
|
||||
|
||||
-- * Executing queries
|
||||
|
||||
-- ** Without results
|
||||
|
||||
-- *** With parameters
|
||||
execute,
|
||||
executeMany,
|
||||
|
||||
-- *** Without parameters
|
||||
execute_,
|
||||
|
||||
-- ** With results
|
||||
|
||||
-- *** With parameters
|
||||
queryListRow,
|
||||
queryListCol,
|
||||
queryMaybeRow,
|
||||
queryMaybeCol,
|
||||
queryOneRow,
|
||||
queryOneCol,
|
||||
|
||||
-- **** With checks
|
||||
queryListRowCheck,
|
||||
queryListColCheck,
|
||||
queryMaybeRowCheck,
|
||||
queryMaybeColCheck,
|
||||
queryOneRowCheck,
|
||||
queryOneColCheck,
|
||||
|
||||
-- *** Without parameters
|
||||
queryListRow_,
|
||||
queryListCol_,
|
||||
queryMaybeRow_,
|
||||
queryMaybeCol_,
|
||||
queryOneRow_,
|
||||
queryOneCol_,
|
||||
|
||||
-- **** With checks
|
||||
queryListRowCheck_,
|
||||
queryListColCheck_,
|
||||
queryMaybeRowCheck_,
|
||||
queryMaybeColCheck_,
|
||||
queryOneRowCheck_,
|
||||
queryOneColCheck_,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad.Trans.Reader (ReaderT (..))
|
||||
import qualified Database.SQLite.Simple as Sqlite
|
||||
import qualified Database.SQLite.Simple.FromField as Sqlite
|
||||
import Unison.Prelude
|
||||
import Unison.Sqlite.Connection (Connection (..))
|
||||
import qualified Unison.Sqlite.Connection as Connection
|
||||
import Unison.Sqlite.Exception (SqliteExceptionReason)
|
||||
import Unison.Sqlite.Sql
|
||||
|
||||
newtype Transaction a
|
||||
= Transaction (Connection -> IO a)
|
||||
-- Omit MonadIO instance because transactions may be retried
|
||||
-- Omit MonadThrow instance so we always throw SqliteException (via *Check) with lots of context
|
||||
deriving (Applicative, Functor, Monad) via (ReaderT Connection IO)
|
||||
|
||||
-- | Run a transaction on the given connection.
|
||||
runTransaction :: MonadIO m => Connection -> Transaction a -> m a
|
||||
runTransaction conn@(Connection _ _ conn0) (Transaction f) =
|
||||
-- TODO some sensible retry logic
|
||||
liftIO do
|
||||
Sqlite.execute_ conn0 "BEGIN"
|
||||
result <- f conn
|
||||
Sqlite.execute_ conn0 "COMMIT"
|
||||
pure result
|
||||
|
||||
-- Without results, with parameters
|
||||
|
||||
execute :: Sqlite.ToRow a => Sql -> a -> Transaction ()
|
||||
execute s params = do
|
||||
Transaction \conn -> Connection.execute conn s params
|
||||
|
||||
executeMany :: Sqlite.ToRow a => Sql -> [a] -> Transaction ()
|
||||
executeMany s params =
|
||||
Transaction \conn -> Connection.executeMany conn s params
|
||||
|
||||
-- Without results, without parameters
|
||||
|
||||
execute_ :: Sql -> Transaction ()
|
||||
execute_ s =
|
||||
Transaction \conn -> Connection.execute_ conn s
|
||||
|
||||
-- With results, with parameters, without checks
|
||||
|
||||
queryListRow :: (Sqlite.FromRow a, Sqlite.ToRow b) => Sql -> b -> Transaction [a]
|
||||
queryListRow s params =
|
||||
Transaction \conn -> Connection.queryListRow conn s params
|
||||
|
||||
queryListCol :: (Sqlite.FromField a, Sqlite.ToRow b) => Sql -> b -> Transaction [a]
|
||||
queryListCol s params =
|
||||
Transaction \conn -> Connection.queryListCol conn s params
|
||||
|
||||
queryMaybeRow :: (Sqlite.FromRow a, Sqlite.ToRow b) => Sql -> b -> Transaction (Maybe a)
|
||||
queryMaybeRow s params =
|
||||
Transaction \conn -> Connection.queryMaybeRow conn s params
|
||||
|
||||
queryMaybeCol :: (Sqlite.FromField a, Sqlite.ToRow b) => Sql -> b -> Transaction (Maybe a)
|
||||
queryMaybeCol s params =
|
||||
Transaction \conn -> Connection.queryMaybeCol conn s params
|
||||
|
||||
queryOneRow :: (Sqlite.FromRow b, Sqlite.ToRow a) => Sql -> a -> Transaction b
|
||||
queryOneRow s params =
|
||||
Transaction \conn -> Connection.queryOneRow conn s params
|
||||
|
||||
queryOneCol :: (Sqlite.FromField b, Sqlite.ToRow a) => Sql -> a -> Transaction b
|
||||
queryOneCol s params =
|
||||
Transaction \conn -> Connection.queryOneCol conn s params
|
||||
|
||||
-- With results, with parameters, with checks
|
||||
|
||||
queryListRowCheck ::
|
||||
(Sqlite.FromRow b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Sql ->
|
||||
a ->
|
||||
([b] -> Either e r) ->
|
||||
Transaction r
|
||||
queryListRowCheck s params check =
|
||||
Transaction \conn -> Connection.queryListRowCheck conn s params check
|
||||
|
||||
queryListColCheck ::
|
||||
(Sqlite.FromField b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Sql ->
|
||||
a ->
|
||||
([b] -> Either e r) ->
|
||||
Transaction r
|
||||
queryListColCheck s params check =
|
||||
Transaction \conn -> Connection.queryListColCheck conn s params check
|
||||
|
||||
queryMaybeRowCheck ::
|
||||
(Sqlite.FromRow b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Sql ->
|
||||
a ->
|
||||
(Maybe b -> Either e r) ->
|
||||
Transaction r
|
||||
queryMaybeRowCheck s params check =
|
||||
Transaction \conn -> Connection.queryMaybeRowCheck conn s params check
|
||||
|
||||
queryMaybeColCheck ::
|
||||
(Sqlite.FromField b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Sql ->
|
||||
a ->
|
||||
(Maybe b -> Either e r) ->
|
||||
Transaction r
|
||||
queryMaybeColCheck s params check =
|
||||
Transaction \conn -> Connection.queryMaybeColCheck conn s params check
|
||||
|
||||
queryOneRowCheck ::
|
||||
(Sqlite.FromRow b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Sql ->
|
||||
a ->
|
||||
(b -> Either e r) ->
|
||||
Transaction r
|
||||
queryOneRowCheck s params check =
|
||||
Transaction \conn -> Connection.queryOneRowCheck conn s params check
|
||||
|
||||
queryOneColCheck ::
|
||||
(Sqlite.FromField b, Sqlite.ToRow a, SqliteExceptionReason e) =>
|
||||
Sql ->
|
||||
a ->
|
||||
(b -> Either e r) ->
|
||||
Transaction r
|
||||
queryOneColCheck s params check =
|
||||
Transaction \conn -> Connection.queryOneColCheck conn s params check
|
||||
|
||||
-- With results, without parameters, without checks
|
||||
|
||||
queryListRow_ :: Sqlite.FromRow a => Sql -> Transaction [a]
|
||||
queryListRow_ s =
|
||||
Transaction \conn -> Connection.queryListRow_ conn s
|
||||
|
||||
queryListCol_ :: Sqlite.FromField a => Sql -> Transaction [a]
|
||||
queryListCol_ s =
|
||||
Transaction \conn -> Connection.queryListCol_ conn s
|
||||
|
||||
queryMaybeRow_ :: Sqlite.FromRow a => Sql -> Transaction (Maybe a)
|
||||
queryMaybeRow_ s =
|
||||
Transaction \conn -> Connection.queryMaybeRow_ conn s
|
||||
|
||||
queryMaybeCol_ :: Sqlite.FromField a => Sql -> Transaction (Maybe a)
|
||||
queryMaybeCol_ s =
|
||||
Transaction \conn -> Connection.queryMaybeCol_ conn s
|
||||
|
||||
queryOneRow_ :: Sqlite.FromRow a => Sql -> Transaction a
|
||||
queryOneRow_ s =
|
||||
Transaction \conn -> Connection.queryOneRow_ conn s
|
||||
|
||||
queryOneCol_ :: Sqlite.FromField a => Sql -> Transaction a
|
||||
queryOneCol_ s =
|
||||
Transaction \conn -> Connection.queryOneCol_ conn s
|
||||
|
||||
-- With results, without parameters, with checks
|
||||
|
||||
queryListRowCheck_ :: (Sqlite.FromRow a, SqliteExceptionReason e) => Sql -> ([a] -> Either e r) -> Transaction r
|
||||
queryListRowCheck_ s check =
|
||||
Transaction \conn -> Connection.queryListRowCheck_ conn s check
|
||||
|
||||
queryListColCheck_ :: (Sqlite.FromField a, SqliteExceptionReason e) => Sql -> ([a] -> Either e r) -> Transaction r
|
||||
queryListColCheck_ s check =
|
||||
Transaction \conn -> Connection.queryListColCheck_ conn s check
|
||||
|
||||
queryMaybeRowCheck_ :: (Sqlite.FromRow a, SqliteExceptionReason e) => Sql -> (Maybe a -> Either e r) -> Transaction r
|
||||
queryMaybeRowCheck_ s check =
|
||||
Transaction \conn -> Connection.queryMaybeRowCheck_ conn s check
|
||||
|
||||
queryMaybeColCheck_ :: (Sqlite.FromField a, SqliteExceptionReason e) => Sql -> (Maybe a -> Either e r) -> Transaction r
|
||||
queryMaybeColCheck_ s check =
|
||||
Transaction \conn -> Connection.queryMaybeColCheck_ conn s check
|
||||
|
||||
queryOneRowCheck_ :: (Sqlite.FromRow a, SqliteExceptionReason e) => Sql -> (a -> Either e r) -> Transaction r
|
||||
queryOneRowCheck_ s check =
|
||||
Transaction \conn -> Connection.queryOneRowCheck_ conn s check
|
||||
|
||||
queryOneColCheck_ :: (Sqlite.FromField a, SqliteExceptionReason e) => Sql -> (a -> Either e r) -> Transaction r
|
||||
queryOneColCheck_ s check =
|
||||
Transaction \conn -> Connection.queryOneColCheck_ conn s check
|
67
lib/unison-sqlite/unison-sqlite.cabal
Normal file
67
lib/unison-sqlite/unison-sqlite.cabal
Normal file
@ -0,0 +1,67 @@
|
||||
cabal-version: 1.12
|
||||
|
||||
-- This file has been generated from package.yaml by hpack version 0.34.4.
|
||||
--
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: unison-sqlite
|
||||
version: 0.0.0
|
||||
homepage: https://github.com/unisonweb/unison#readme
|
||||
bug-reports: https://github.com/unisonweb/unison/issues
|
||||
copyright: Copyright (C) 2013-2021 Unison Computing, PBC and contributors
|
||||
build-type: Simple
|
||||
|
||||
source-repository head
|
||||
type: git
|
||||
location: https://github.com/unisonweb/unison
|
||||
|
||||
library
|
||||
exposed-modules:
|
||||
Unison.Sqlite
|
||||
Unison.Sqlite.Connection
|
||||
Unison.Sqlite.DB
|
||||
Unison.Sqlite.Transaction
|
||||
other-modules:
|
||||
Unison.Sqlite.DataVersion
|
||||
Unison.Sqlite.Exception
|
||||
Unison.Sqlite.JournalMode
|
||||
Unison.Sqlite.Sql
|
||||
hs-source-dirs:
|
||||
src
|
||||
default-extensions:
|
||||
BlockArguments
|
||||
ConstraintKinds
|
||||
DeriveAnyClass
|
||||
DeriveFunctor
|
||||
DerivingStrategies
|
||||
DerivingVia
|
||||
DoAndIfThenElse
|
||||
DuplicateRecordFields
|
||||
FlexibleContexts
|
||||
FlexibleInstances
|
||||
GADTs
|
||||
GeneralizedNewtypeDeriving
|
||||
KindSignatures
|
||||
LambdaCase
|
||||
MultiParamTypeClasses
|
||||
NamedFieldPuns
|
||||
NumericUnderscores
|
||||
OverloadedStrings
|
||||
ScopedTypeVariables
|
||||
TupleSections
|
||||
TypeApplications
|
||||
ViewPatterns
|
||||
ghc-options: -Wall
|
||||
build-depends:
|
||||
base
|
||||
, direct-sqlite
|
||||
, exceptions
|
||||
, mtl
|
||||
, recover-rtti
|
||||
, sqlite-simple
|
||||
, text
|
||||
, transformers
|
||||
, unison-prelude
|
||||
, unliftio
|
||||
, unliftio-core
|
||||
default-language: Haskell2010
|
@ -22,6 +22,7 @@ packages:
|
||||
- codebase2/util-serialization
|
||||
- codebase2/util-term
|
||||
- lib/unison-prelude
|
||||
- lib/unison-sqlite
|
||||
- lib/unison-util-relation
|
||||
|
||||
#compiler-check: match-exact
|
||||
@ -41,6 +42,8 @@ extra-deps:
|
||||
- fuzzyfind-3.0.0
|
||||
- monad-validate-1.2.0.0@sha256:9850f408431098b28806dd464b6825a88a0b56c84f380d7fe0454c1df9d6f881,3505
|
||||
- NanoID-3.1.0@sha256:9118ab00e8650b5a56a10c90295d357eb77a8057a598b7e56dfedc9c6d53c77d,1524
|
||||
# not in lts-18.13
|
||||
- recover-rtti-0.4.0.0@sha256:2ce1e031ec0e34d736fa45f0149bbd55026f614939dc90ffd14a9c5d24093ff4,4423
|
||||
|
||||
ghc-options:
|
||||
# All packages
|
||||
|
Loading…
Reference in New Issue
Block a user