packages feed

preql 0.2 → 0.3

raw patch · 12 files changed

+399/−222 lines, 12 filesdep ~aesondep ~basedep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: aeson, base, bytestring, postgresql-libpq, tasty, tasty-hunit, template-haskell, text, uuid, vector

API changes (from Hackage documentation)

- Preql: query :: (SQL m, ToSql p, FromSql r) => (Query, p) -> m (Vector r)
- Preql: query_ :: (SQL m, ToSql p) => (Query, p) -> m ()
- Preql: runTransaction :: SQL m => Transaction a -> m a
- Preql.Effect: data Query
- Preql.Effect.Internal: class Monad m => SQL (m :: * -> *)
- Preql.Effect.Internal: instance (Control.Monad.Trans.Class.MonadTrans t, GHC.Base.Monad (t m), Preql.Effect.Internal.SQL m) => Preql.Effect.Internal.SQL (t m)
- Preql.Effect.Internal: instance Preql.Effect.Internal.SQL (Control.Monad.Trans.Reader.ReaderT Database.PostgreSQL.LibPQ.Internal.Connection GHC.Types.IO)
- Preql.Effect.Internal: instance Preql.Effect.Internal.SQL Preql.Effect.Internal.Transaction
- Preql.Effect.Internal: query :: (SQL m, ToSql p, FromSql r) => (Query, p) -> m (Vector r)
- Preql.Effect.Internal: query_ :: (SQL m, ToSql p) => (Query, p) -> m ()
- Preql.Effect.Internal: runTransaction :: SQL m => Transaction a -> m a
- Preql.Effect.Internal: runTransactionIO :: Transaction a -> Connection -> IO (Either QueryError a)
- Preql.Imports: data (:~:) (a :: k) (b :: k) :: forall k. () => k -> k -> Type
- Preql.Imports: data (:~~:) (a :: k1) (b :: k2) :: forall k1 k2. () => k1 -> k2 -> Type
- Preql.Imports: newtype Const a (b :: k) :: forall k. () => Type -> k -> Type
+ Preql: queryOn :: (SQL m, ToSql p, FromSql r, MonadIO m) => Connection -> (Query, p) -> m (Vector r)
+ Preql: queryOn_ :: (SQL m, ToSql p, MonadIO m) => Connection -> (Query, p) -> m ()
+ Preql: runTransaction' :: (SQL m, MonadIO m) => IsolationLevel -> Transaction a -> m a
+ Preql: withConnection :: SQL m => (Connection -> m a) -> m a
+ Preql.Effect: class Monad m => SqlQuery (m :: * -> *)
+ Preql.Effect: instance (GHC.Base.Monad m, Preql.Effect.SQL m) => Preql.Effect.SqlQuery m
+ Preql.Effect: instance (GHC.Base.Monoid w, Preql.Effect.SQL m) => Preql.Effect.SQL (Control.Monad.Trans.RWS.Lazy.RWST r w s m)
+ Preql.Effect: instance (GHC.Base.Monoid w, Preql.Effect.SQL m) => Preql.Effect.SQL (Control.Monad.Trans.RWS.Strict.RWST r w s m)
+ Preql.Effect: instance Preql.Effect.SQL (Control.Monad.Trans.Reader.ReaderT Database.PostgreSQL.LibPQ.Internal.Connection GHC.Types.IO)
+ Preql.Effect: instance Preql.Effect.SQL m => Preql.Effect.SQL (Control.Monad.Trans.Except.ExceptT e m)
+ Preql.Effect: instance Preql.Effect.SQL m => Preql.Effect.SQL (Control.Monad.Trans.Maybe.MaybeT m)
+ Preql.Effect: instance Preql.Effect.SQL m => Preql.Effect.SQL (Control.Monad.Trans.Reader.ReaderT r m)
+ Preql.Effect: instance Preql.Effect.SQL m => Preql.Effect.SQL (Control.Monad.Trans.State.Lazy.StateT s m)
+ Preql.Effect: instance Preql.Effect.SQL m => Preql.Effect.SQL (Control.Monad.Trans.State.Strict.StateT s m)
+ Preql.Effect: instance Preql.Effect.SqlQuery Preql.Effect.Internal.Transaction
+ Preql.Effect: queryOn :: (SQL m, ToSql p, FromSql r, MonadIO m) => Connection -> (Query, p) -> m (Vector r)
+ Preql.Effect: queryOn_ :: (SQL m, ToSql p, MonadIO m) => Connection -> (Query, p) -> m ()
+ Preql.Effect: runTransaction' :: (SQL m, MonadIO m) => IsolationLevel -> Transaction a -> m a
+ Preql.Effect: withConnection :: SQL m => (Connection -> m a) -> m a
+ Preql.Imports: data (a :: k1) :~~: (b :: k2)
+ Preql.Imports: data Proxy (t :: k)
+ Preql.Imports: newtype Const a (b :: k)
+ Preql.QuasiQuoter.Raw.Lex: utf8Encode' :: Char -> (Word8, [Word8])
+ Preql.QuasiQuoter.Raw.TH: tupleE :: [Exp] -> Exp
+ Preql.Wire: Oid :: Oid -> PgType
+ Preql.Wire: ReadCommitted :: IsolationLevel
+ Preql.Wire: RepeatableRead :: IsolationLevel
+ Preql.Wire: Serializable :: IsolationLevel
+ Preql.Wire: TypeName :: Text -> PgType
+ Preql.Wire: data IsolationLevel
+ Preql.Wire: data PgType
+ Preql.Wire.Errors: Oid :: Oid -> PgType
+ Preql.Wire.Errors: TypeName :: Text -> PgType
+ Preql.Wire.Errors: data PgType
+ Preql.Wire.Errors: instance Data.Aeson.Types.FromJSON.FromJSON Preql.Wire.Errors.PgType
+ Preql.Wire.Errors: instance Data.Aeson.Types.ToJSON.ToJSON Preql.Wire.Errors.PgType
+ Preql.Wire.Errors: instance GHC.Classes.Eq Preql.Wire.Errors.PgType
+ Preql.Wire.Errors: instance GHC.Show.Show Preql.Wire.Errors.PgType
+ Preql.Wire.FromSql: instance Preql.Wire.FromSql.FromSql Database.PostgreSQL.LibPQ.Oid
+ Preql.Wire.FromSql: instance Preql.Wire.FromSql.FromSqlField Database.PostgreSQL.LibPQ.Oid
+ Preql.Wire.Query: ReadCommitted :: IsolationLevel
+ Preql.Wire.Query: RepeatableRead :: IsolationLevel
+ Preql.Wire.Query: Serializable :: IsolationLevel
+ Preql.Wire.Query: begin :: Connection -> IsolationLevel -> IO (Either QueryError ())
+ Preql.Wire.Query: commit :: Connection -> IO (Either QueryError ())
+ Preql.Wire.Query: data IsolationLevel
+ Preql.Wire.Query: instance GHC.Classes.Eq Preql.Wire.Query.IsolationLevel
+ Preql.Wire.Query: instance GHC.Classes.Ord Preql.Wire.Query.IsolationLevel
+ Preql.Wire.Query: instance GHC.Enum.Bounded Preql.Wire.Query.IsolationLevel
+ Preql.Wire.Query: instance GHC.Enum.Enum Preql.Wire.Query.IsolationLevel
+ Preql.Wire.Query: instance GHC.Read.Read Preql.Wire.Query.IsolationLevel
+ Preql.Wire.Query: instance GHC.Show.Show Preql.Wire.Query.IsolationLevel
+ Preql.Wire.Query: lookupType :: Connection -> PgType -> IO (Either QueryError Oid)
+ Preql.Wire.Query: rollback :: Connection -> IO (Either QueryError ())
- Preql: TypeMismatch :: Oid -> Oid -> Int -> Maybe Text -> TypeMismatch
+ Preql: TypeMismatch :: PgType -> Oid -> Int -> Maybe Text -> TypeMismatch
- Preql: [expected] :: TypeMismatch -> Oid
+ Preql: [expected] :: TypeMismatch -> PgType
- Preql: class Monad m => SQL (m :: * -> *)
+ Preql: class SqlQuery m => SQL (m :: * -> *)
- Preql: runTransactionIO :: Transaction a -> Connection -> IO (Either QueryError a)
+ Preql: runTransactionIO :: IsolationLevel -> Transaction a -> Connection -> IO (Either QueryError a)
- Preql.Effect: class Monad m => SQL (m :: * -> *)
+ Preql.Effect: class SqlQuery m => SQL (m :: * -> *)
- Preql.Effect: query :: (SQL m, ToSql p, FromSql r) => (Query, p) -> m (Vector r)
+ Preql.Effect: query :: (SqlQuery m, ToSql p, FromSql r) => (Query, p) -> m (Vector r)
- Preql.Effect: query_ :: (SQL m, ToSql p) => (Query, p) -> m ()
+ Preql.Effect: query_ :: (SqlQuery m, ToSql p) => (Query, p) -> m ()
- Preql.Effect: runTransactionIO :: Transaction a -> Connection -> IO (Either QueryError a)
+ Preql.Effect: runTransactionIO :: IsolationLevel -> Transaction a -> Connection -> IO (Either QueryError a)
- Preql.Imports: Const :: a -> Const a
+ Preql.Imports: Const :: a -> Const a (b :: k)
- Preql.Imports: Proxy :: Proxy
+ Preql.Imports: Proxy :: Proxy (t :: k)
- Preql.Imports: WrapArrow :: a b c -> WrappedArrow b c
+ Preql.Imports: WrapArrow :: a b c -> WrappedArrow (a :: Type -> Type -> Type) b c
- Preql.Imports: WrapMonad :: m a -> WrappedMonad a
+ Preql.Imports: WrapMonad :: m a -> WrappedMonad (m :: Type -> Type) a
- Preql.Imports: [HRefl] :: forall k1 k2 (a :: k1) (b :: k2). () => a :~~: a
+ Preql.Imports: [HRefl] :: forall k1 (a :: k1). a :~~: a
- Preql.Imports: [Refl] :: forall k (a :: k) (b :: k). () => a :~: a
+ Preql.Imports: [Refl] :: forall k (a :: k). a :~: a
- Preql.Imports: [getConst] :: Const a -> a
+ Preql.Imports: [getConst] :: Const a (b :: k) -> a
- Preql.Imports: [unwrapArrow] :: WrappedArrow b c -> a b c
+ Preql.Imports: [unwrapArrow] :: WrappedArrow (a :: Type -> Type -> Type) b c -> a b c
- Preql.Imports: [unwrapMonad] :: WrappedMonad a -> m a
+ Preql.Imports: [unwrapMonad] :: WrappedMonad (m :: Type -> Type) a -> m a
- Preql.Imports: catMaybes :: () => [Maybe a] -> [a]
+ Preql.Imports: catMaybes :: [Maybe a] -> [a]
- Preql.Imports: eqT :: (Typeable a, Typeable b) => Maybe (a :~: b)
+ Preql.Imports: eqT :: forall k (a :: k) (b :: k). (Typeable a, Typeable b) => Maybe (a :~: b)
- Preql.Imports: fromMaybe :: () => a -> Maybe a -> a
+ Preql.Imports: fromMaybe :: a -> Maybe a -> a
- Preql.Imports: gcast :: (Typeable a, Typeable b) => c a -> Maybe (c b)
+ Preql.Imports: gcast :: forall k (a :: k) (b :: k) c. (Typeable a, Typeable b) => c a -> Maybe (c b)
- Preql.Imports: gcast1 :: (Typeable t, Typeable t') => c (t a) -> Maybe (c (t' a))
+ Preql.Imports: gcast1 :: forall k1 k2 c (t :: k2 -> k1) (t' :: k2 -> k1) (a :: k2). (Typeable t, Typeable t') => c (t a) -> Maybe (c (t' a))
- Preql.Imports: gcast2 :: (Typeable t, Typeable t') => c (t a b) -> Maybe (c (t' a b))
+ Preql.Imports: gcast2 :: forall k1 k2 k3 c (t :: k2 -> k3 -> k1) (t' :: k2 -> k3 -> k1) (a :: k2) (b :: k3). (Typeable t, Typeable t') => c (t a b) -> Maybe (c (t' a b))
- Preql.Imports: typeRep :: Typeable a => proxy a -> TypeRep
+ Preql.Imports: typeRep :: forall k proxy (a :: k). Typeable a => proxy a -> TypeRep
- Preql.QuasiQuoter.Raw.Lex: alexScanUser :: () => t -> (AlexPosn, Char, [Byte], String) -> Int -> AlexReturn (AlexAction LocToken)
+ Preql.QuasiQuoter.Raw.Lex: alexScanUser :: t -> (AlexPosn, Char, [Byte], String) -> Int -> AlexReturn (AlexAction LocToken)
- Preql.QuasiQuoter.Raw.Lex: alex_accept :: () => Array Int (AlexAcc user)
+ Preql.QuasiQuoter.Raw.Lex: alex_accept :: Array Int (AlexAcc user)
- Preql.QuasiQuoter.Raw.Lex: alex_scan_tkn :: () => t1 -> t2 -> Int# -> AlexInput -> Int# -> AlexLastAcc -> (AlexLastAcc, (AlexPosn, Char, [Byte], String))
+ Preql.QuasiQuoter.Raw.Lex: alex_scan_tkn :: t1 -> t2 -> Int# -> AlexInput -> Int# -> AlexLastAcc -> (AlexLastAcc, (AlexPosn, Char, [Byte], String))
- Preql.QuasiQuoter.Raw.Lex: begin :: () => Int -> p1 -> p2 -> Alex LocToken
+ Preql.QuasiQuoter.Raw.Lex: begin :: Int -> p1 -> p2 -> Alex LocToken
- Preql.QuasiQuoter.Raw.Lex: skip :: () => p1 -> p2 -> Alex LocToken
+ Preql.QuasiQuoter.Raw.Lex: skip :: p1 -> p2 -> Alex LocToken
- Preql.Wire: FieldDecoder :: Oid -> BinaryParser a -> FieldDecoder a
+ Preql.Wire: FieldDecoder :: PgType -> BinaryParser a -> FieldDecoder a
- Preql.Wire: TypeMismatch :: Oid -> Oid -> Int -> Maybe Text -> TypeMismatch
+ Preql.Wire: TypeMismatch :: PgType -> Oid -> Int -> Maybe Text -> TypeMismatch
- Preql.Wire: [expected] :: TypeMismatch -> Oid
+ Preql.Wire: [expected] :: TypeMismatch -> PgType
- Preql.Wire: decodeVector :: RowDecoder a -> Result -> IO (Either QueryError (Vector a))
+ Preql.Wire: decodeVector :: (PgType -> IO (Either QueryError Oid)) -> RowDecoder a -> Result -> IO (Either QueryError (Vector a))
- Preql.Wire.Errors: TypeMismatch :: Oid -> Oid -> Int -> Maybe Text -> TypeMismatch
+ Preql.Wire.Errors: TypeMismatch :: PgType -> Oid -> Int -> Maybe Text -> TypeMismatch
- Preql.Wire.Errors: [expected] :: TypeMismatch -> Oid
+ Preql.Wire.Errors: [expected] :: TypeMismatch -> PgType
- Preql.Wire.FromSql: FieldDecoder :: Oid -> BinaryParser a -> FieldDecoder a
+ Preql.Wire.FromSql: FieldDecoder :: PgType -> BinaryParser a -> FieldDecoder a
- Preql.Wire.FromSql: decodeVector :: RowDecoder a -> Result -> IO (Either QueryError (Vector a))
+ Preql.Wire.FromSql: decodeVector :: (PgType -> IO (Either QueryError Oid)) -> RowDecoder a -> Result -> IO (Either QueryError (Vector a))
- Preql.Wire.Internal: RowDecoder :: [Oid] -> InternalDecoder a -> RowDecoder a
+ Preql.Wire.Internal: RowDecoder :: [PgType] -> InternalDecoder a -> RowDecoder a

Files

CHANGELOG.md view
@@ -1,3 +1,15 @@+# 0.3 (2020-06-18)++- lookup types by name when OID is not known statically+- specify isolation level when running a transaction+- stop trying to fake nested transactions+- provide direct access to a Connection when that control is needed++# 0.2 (2020-03-31)++- now we have documentation+- rearrange some code, re-export modules to support docs+ # 0.1 (2020-02-29)  0.1 highlights include:
preql.cabal view
@@ -1,111 +1,125 @@ cabal-version: 1.18-name: preql-version: 0.2-license: BSD3-license-file: LICENSE-maintainer: bergey@teallabs.org-author: Daniel Bergey-homepage: https://github.com/bergey/preql#readme-bug-reports: https://github.com/bergey/preql/issues-synopsis: safe PostgreSQL queries using Quasiquoters-description:-    Before you Post(gres)QL, preql.-    .-    @preql@ provides a low-level interface to PostgreSQL and a quasiquoter that converts-    inline variable names to SQL parameters.  Higher-level interfaces, checking SQL syntax &-    schema, are planned.-    .-    the [quickstart](https://github.com/bergey/preql#quickstart)-    or the [vision](https://github.com/bergey/preql#vision-parsing-sql-in-haskell-quasiquotes)-    .-    Most applications will want to import the top-level module @Preql@.  When writing @SQL@-    instances or your own higher-level abstractions, you may want the lower-level,-    IO-specific functions in @Preql.Wire@, not all of which are re-exported from @Preql@.-    .-category: Database, PostgreSQL-build-type: Simple-extra-doc-files: CHANGELOG.md +-- This file has been generated from package.yaml by hpack version 0.33.1.+--+-- see: https://github.com/sol/hpack+--+-- hash: 9ee4ad1565dfa854b29bb325c372b2238843844d5c1f93696d018675540809e9++name:           preql+version:        0.3+synopsis:       safe PostgreSQL queries using Quasiquoters+description:    Before you Post(gres)QL, preql.+                .+                @preql@ provides a low-level interface to PostgreSQL and a quasiquoter that converts+                inline variable names to SQL parameters.  Higher-level interfaces, checking SQL syntax &+                schema, are planned.+                .+                the [quickstart](https://github.com/bergey/preql#quickstart) +                or the [vision](https://github.com/bergey/preql#vision-parsing-sql-in-haskell-quasiquotes)+                .+                Most applications will want to import the top-level module @Preql@.  When writing @SQL@+                instances or your own higher-level abstractions, you may want the lower-level,+                IO-specific functions in @Preql.Wire@, not all of which are re-exported from @Preql@.+                .+category:       Database, PostgreSQL+homepage:       https://github.com/bergey/preql#readme+bug-reports:    https://github.com/bergey/preql/issues+author:         Daniel Bergey+maintainer:     bergey@teallabs.org+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-doc-files:+    CHANGELOG.md+ source-repository head-    type: git-    location: https://github.com/bergey/preql+  type: git+  location: https://github.com/bergey/preql  library-    exposed-modules:-        Preql-        Preql.Effect-        Preql.Effect.Internal-        Preql.Imports-        Preql.QuasiQuoter.Raw.Lex-        Preql.QuasiQuoter.Raw.TH-        Preql.Wire-        Preql.Wire.Errors-        Preql.Wire.FromSql-        Preql.Wire.Internal-        Preql.Wire.Orphans-        Preql.Wire.Query-        Preql.Wire.ToSql-        Preql.Wire.Tuples-        Preql.Wire.TypeInfo.Static-        Preql.Wire.TypeInfo.Types-        Preql.Wire.Types-    build-tools: alex -any, happy -any-    hs-source-dirs: src-    other-modules:-        Paths_preql-    default-language: Haskell2010-    default-extensions: OverloadedStrings-    build-depends:-        aeson >=1.3.1 && <1.5,-        array >=0.5.2 && <0.6,-        base >=4.11 && <4.14,-        binary-parser >=0.5.5 && <0.6,-        bytestring >=0.10.8 && <0.11,-        bytestring-strict-builder >=0.4.5 && <0.5,-        contravariant >=1.4.1 && <1.6,-        free >=5.0.2 && <5.2,-        mtl >=2.2.2 && <2.3,-        postgresql-binary >=0.12.1 && <0.13,-        postgresql-libpq >=0.9.4 && <0.10,-        scientific >=0.3.6 && <0.4,-        template-haskell >=2.13.0 && <2.16,-        text >=1.2.3 && <1.3,-        th-lift-instances >=0.1.11 && <0.2,-        time >=1.8.0 && <1.10,-        transformers >=0.5.5 && <0.6,-        uuid >=1.3.13 && <1.4,-        vector >=0.12.0 && <0.13+  exposed-modules:+      Preql+      Preql.Effect+      Preql.Effect.Internal+      Preql.Imports+      Preql.QuasiQuoter.Raw.Lex+      Preql.QuasiQuoter.Raw.TH+      Preql.Wire+      Preql.Wire.Errors+      Preql.Wire.FromSql+      Preql.Wire.Internal+      Preql.Wire.Orphans+      Preql.Wire.Query+      Preql.Wire.ToSql+      Preql.Wire.Tuples+      Preql.Wire.TypeInfo.Static+      Preql.Wire.TypeInfo.Types+      Preql.Wire.Types+  other-modules:+      Paths_preql+  hs-source-dirs:+      src+  default-extensions: OverloadedStrings+  build-tools:+      alex+    , happy+  build-depends:+      aeson >=1.3.1 && <1.6+    , array >=0.5.2 && <0.6+    , base >=4.11 && <4.15+    , binary-parser >=0.5.5 && <0.6+    , bytestring >=0.10.8 && <0.11+    , bytestring-strict-builder >=0.4.5 && <0.5+    , contravariant >=1.4.1 && <1.6+    , free >=5.0.2 && <5.2+    , mtl >=2.2.2 && <2.3+    , postgresql-binary >=0.12.1 && <0.13+    , postgresql-libpq >=0.9.4 && <0.10+    , scientific >=0.3.6 && <0.4+    , template-haskell >=2.13.0 && <2.17+    , text >=1.2.3 && <1.3+    , th-lift-instances >=0.1.11 && <0.2+    , time >=1.8.0 && <1.10+    , transformers >=0.5.5 && <0.6+    , uuid >=1.3.13 && <1.4+    , vector >=0.12.0 && <0.13+  default-language: Haskell2010  test-suite tests-    type: exitcode-stdio-1.0-    main-is: Test.hs-    build-tools: alex -any, happy -any-    hs-source-dirs: test-    other-modules:-        Test.Wire-        Paths_preql-    default-language: Haskell2010-    default-extensions: OverloadedStrings-    build-depends:-        aeson >=1.3.1 && <1.5,-        array >=0.5.2 && <0.6,-        base >=4.11 && <4.14,-        binary-parser >=0.5.5 && <0.6,-        bytestring <0.11,-        bytestring-strict-builder >=0.4.5 && <0.5,-        contravariant >=1.4.1 && <1.6,-        free >=5.0.2 && <5.2,-        mtl >=2.2.2 && <2.3,-        postgresql-binary >=0.12.1 && <0.13,-        postgresql-libpq <0.10,-        preql -any,-        scientific >=0.3.6 && <0.4,-        tasty <1.3,-        tasty-hunit <0.11,-        template-haskell >=2.13.0 && <2.16,-        text <1.3,-        th-lift-instances >=0.1.11 && <0.2,-        time >=1.8.0 && <1.10,-        transformers >=0.5.5 && <0.6,-        uuid <1.4,-        vector <0.13+  type: exitcode-stdio-1.0+  main-is: Test.hs+  other-modules:+      Test.Wire+      Test.Wire.Enum+      Paths_preql+  hs-source-dirs:+      test+  default-extensions: OverloadedStrings+  build-tools:+      alex+    , happy+  build-depends:+      aeson >=1.3.1 && <1.6+    , array >=0.5.2 && <0.6+    , base >=4.11 && <4.15+    , binary-parser >=0.5.5 && <0.6+    , bytestring >=0.10.8 && <0.11+    , bytestring-strict-builder >=0.4.5 && <0.5+    , contravariant >=1.4.1 && <1.6+    , free >=5.0.2 && <5.2+    , mtl >=2.2.2 && <2.3+    , postgresql-binary >=0.12.1 && <0.13+    , postgresql-libpq >=0.9.4 && <0.10+    , preql+    , scientific >=0.3.6 && <0.4+    , tasty >=1.1 && <1.4+    , tasty-hunit >=0.10 && <0.11+    , template-haskell >=2.13.0 && <2.17+    , text >=1.2.3 && <1.3+    , th-lift-instances >=0.1.11 && <0.2+    , time >=1.8.0 && <1.10+    , transformers >=0.5.5 && <0.6+    , uuid >=1.3.13 && <1.4+    , vector >=0.12.0 && <0.13+  default-language: Haskell2010
src/Preql/Effect.hs view
@@ -1,8 +1,149 @@+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleInstances #-}+ -- | SQL Effect class, basically capturing some way of accessing a database.  module Preql.Effect-    ( Query, SQL(..), Transaction, runTransactionIO+    ( module Preql.Effect, Transaction     ) where  import Preql.Effect.Internal-import Preql.Wire.Internal+import Preql.Imports+import Preql.Wire++import Control.Exception (throwIO)+import Control.Monad.Trans.Class (MonadTrans(..))+import Control.Monad.Trans.Except (ExceptT(..), runExceptT)+import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT)+import Control.Monad.Trans.Reader (ReaderT(..), ask, runReaderT)+import Database.PostgreSQL.LibPQ (Connection)+import qualified Control.Monad.Trans.State.Lazy as SL+import qualified Control.Monad.Trans.State.Strict as SS+import qualified Control.Monad.Trans.RWS.Lazy as L+import qualified Control.Monad.Trans.RWS.Strict as S++import qualified Preql.Wire.Query as W++-- | An Effect class for running SQL queries.  You can think of this as a context+-- specifying a particular Postgres connection (or connection pool).  A minimal instance+-- defines @withConnection@.+--+-- Override the remaining methods to log errors before rethrowing, or not to rethrow.+class SqlQuery m => SQL (m :: * -> *) where+    -- | Run multiple queries in a transaction.+    runTransaction' :: IsolationLevel -> Transaction a -> m a+    default runTransaction' :: MonadIO m => IsolationLevel -> Transaction a -> m a+    runTransaction' level t = withConnection $ \conn ->+        liftIO $ either throwIO pure =<< runTransactionIO level t conn++    -- | @runTransaction@ covers the most common patterns of+    -- mult-statement transactions.  @withConnection@ is useful when+    -- you want more control, or want to override the defaults that+    -- your instance defines.  For example:+    --      - change the number of retries+    --      - interleave calls to other services with the Postgres transaction+    --      - ensure a prepared statement is shared among successive transactions+    withConnection :: (Connection -> m a) -> m a++    -- | Run a query on the specified 'Connection'+    queryOn :: (ToSql p, FromSql r) => Connection -> (Query, p) -> m (Vector r)+    default queryOn :: (ToSql p, FromSql r, MonadIO m) => Connection -> (Query, p) -> m (Vector r)+    queryOn conn (q, p) = liftIO $ either throwIO pure =<< W.query conn q p++    queryOn_ :: ToSql p => Connection -> (Query, p) -> m ()+    default queryOn_ :: (ToSql p, MonadIO m) => Connection -> (Query, p) -> m ()+    queryOn_ conn (q, p) = liftIO $ either throwIO pure =<< W.query_ conn q p++-- | Run a Transaction with full Serializable isolation.+runTransaction :: SQL m => Transaction a -> m a+runTransaction = runTransaction' Serializable++-- | SqlQuery is separate from 'SQL' so that nested 'Transaction's are+-- statically prevented.  @query@ can be used directly within any+-- 'SQL' monad (running a single-statement transaction), or within a+-- 'Transaction'.+--+-- Users should not need to define instances, as every @SQL@ instance+-- implies a @SqlQuery@ instance.+class Monad m => SqlQuery (m :: * -> *) where+    -- | Run a parameterized query that returns data.  The tuple argument is typically provided by+    -- the 'sql' Quasiquoter.+    query :: (ToSql p, FromSql r) => (Query, p) -> m (Vector r)++    -- | Run a parameterized query that does not return data.+    query_ :: ToSql p => (Query, p) -> m ()++-- | Most larger applications will define an instance; this one is suitable to test out the library.+instance SQL (ReaderT Connection IO) where+    withConnection = (ask >>=)++instance {-# OVERLAPPABLE #-} (Monad m, SQL m) => SqlQuery m where+    query qp = withConnection (\conn -> queryOn conn qp)+    query_ q = withConnection (\conn -> queryOn_ conn q)++-- * Transactions++-- | Run the provided 'Transaction'.  If it fails with a 'QueryError', roll back.+runTransactionIO :: IsolationLevel -> Transaction a -> Connection -> IO (Either QueryError a)+runTransactionIO level (Transaction m) conn = do+    either throwIO pure =<< W.begin conn level+    e_a <- runReaderT (runExceptT m) conn+    void $ case e_a of+        Left _ -> W.rollback conn+        Right _ -> W.commit conn+    return e_a++-- | The same @query@ methods can be used within a @Transaction@.+-- Nested @Transactions@ are implemented using savepoints.+instance SqlQuery Transaction where+    query (q, p) = Transaction (ExceptT (ReaderT (\conn -> W.query conn q p)))+    query_ (q, p) = Transaction (ExceptT (ReaderT (\conn -> W.query_ conn q p)))++-- Transformer instances+-- These are all the same, except for the @withConnection@ definitions.++instance SQL m => SQL (ExceptT e m) where+    withConnection f = ExceptT (withConnection (runExceptT . f))+    runTransaction' level t = lift $ runTransaction' level t+    queryOn conn qp = lift $ queryOn conn qp+    queryOn_ conn q = lift $ queryOn_ conn q++instance {-# OVERLAPPABLE #-} SQL m => SQL (ReaderT r m) where+    withConnection f = ReaderT (\r -> withConnection (\conn -> runReaderT (f conn) r))+    runTransaction' level t = lift $ runTransaction' level t+    queryOn conn qp = lift $ queryOn conn qp+    queryOn_ conn q = lift $ queryOn_ conn q++instance SQL m => SQL (MaybeT m) where+    withConnection f = MaybeT (withConnection (runMaybeT . f))+    runTransaction' level t = lift $ runTransaction' level t+    queryOn conn qp = lift $ queryOn conn qp+    queryOn_ conn q = lift $ queryOn_ conn q++instance SQL m => SQL (SL.StateT s m) where+    withConnection f = SL.StateT (\s -> withConnection (\conn -> SL.runStateT (f conn) s))+    runTransaction' level t = lift $ runTransaction' level t+    queryOn conn qp = lift $ queryOn conn qp+    queryOn_ conn q = lift $ queryOn_ conn q++instance SQL m => SQL (SS.StateT s m) where+    withConnection f = SS.StateT (\s -> withConnection (\conn -> SS.runStateT (f conn) s))+    runTransaction' level t = lift $ runTransaction' level t+    queryOn conn qp = lift $ queryOn conn qp+    queryOn_ conn q = lift $ queryOn_ conn q++instance (Monoid w, SQL m) => SQL (S.RWST r w s m) where+    withConnection f = S.RWST (\r s  -> withConnection (\conn -> S.runRWST (f conn) r s))+    runTransaction' level t = lift $ runTransaction' level t+    queryOn conn qp = lift $ queryOn conn qp+    queryOn_ conn q = lift $ queryOn_ conn q++instance (Monoid w, SQL m) => SQL (L.RWST r w s m) where+    withConnection f = L.RWST (\r s  -> withConnection (\conn -> L.runRWST (f conn) r s))+    runTransaction' level t = lift $ runTransaction' level t+    queryOn conn qp = lift $ queryOn conn qp+    queryOn_ conn q = lift $ queryOn_ conn q
src/Preql/Effect/Internal.hs view
@@ -1,90 +1,17 @@-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}  -- | We use IO in the representation of Transaction, but we don't want to allow arbitrary IO, only -- SQL queries.  So keep it private.------ The SQL class refers to Transaction, and the Transaction instance refers to the class, so it all--- needs to be here.  module Preql.Effect.Internal where -import Preql.Imports import Preql.Wire-import Preql.Wire.Internal -import Control.Exception (throwIO)-import Control.Monad.Trans.Class (MonadTrans(..))-import Control.Monad.Trans.Except (ExceptT(..), runExceptT)-import Control.Monad.Trans.Reader (ReaderT(..), ask, runReaderT)+import Control.Monad.Trans.Except (ExceptT(..))+import Control.Monad.Trans.Reader (ReaderT(..)) import Database.PostgreSQL.LibPQ (Connection) -import qualified Preql.Wire.Query as W---- | An Effect class for running SQL queries.  You can think of this as a context--- specifying a particular Postgres connection (or connection pool).  A minimal instance--- defines @runTransaction@.  A typical instance will use 'runTransactionIO' or functions--- in 'Preql.Wire.Query' and log & rethrow errors.-class Monad m => SQL (m :: * -> *) where-    -- | Run a parameterized query that returns data.  The tuple argument is typically provided by-    -- the 'sql' Quasiquoter.-    query :: (ToSql p, FromSql r) => (Query, p) -> m (Vector r)-    query = runTransaction . query--    -- | Run a parameterized query that does not return data.-    query_ :: ToSql p => (Query, p) -> m ()-    query_ = runTransaction . query_--    -- | Run multiple queries in a transaction.-    runTransaction :: Transaction a -> m a-    -- TODO add variant with isolation level---- | Most larger applications will define an instance; this one is suitable to test out the library.-instance SQL (ReaderT Connection IO) where-    runTransaction t = do-        conn <- ask-        lift (either throwIO pure =<< runTransactionIO t conn)---- | Lift through any monad transformer without a more specific instance.-instance {-# OVERLAPPABLE #-} (MonadTrans t, Monad (t m), SQL m) => SQL (t m) where-    query = lift . query-    query_ = lift . query_-    runTransaction = lift . runTransaction---- * Transactions- -- | A Transaction can only contain SQL queries (and pure functions). newtype Transaction a = Transaction (ExceptT QueryError (ReaderT Connection IO) a)     deriving newtype (Functor, Applicative, Monad)---- | Run the provided 'Transaction'.  If it fails with a 'QueryError', roll back.-runTransactionIO :: Transaction a -> Connection -> IO (Either QueryError a)-runTransactionIO (Transaction m) conn = do-    void $ W.query_ conn (Query "BEGIN TRANSACTION") ()-    e_a <- runReaderT (runExceptT m) conn-    void $ case e_a of-        Left _ -> W.query_ conn (Query "ROLLBACK") ()-        Right _ -> W.query_ conn (Query "COMMIT") ()-    return e_a---- | The same @query@ methods can be used within a @Transaction@.--- Nested @Transactions@ are implemented using savepoints.-instance SQL Transaction where-    query (q, p) = Transaction (ExceptT (ReaderT (\conn -> W.query conn q p)))-    query_ (q, p) = Transaction (ExceptT (ReaderT (\conn -> W.query_ conn q p)))-    runTransaction (Transaction t) = do-        query_ (Query "SAVEPOINT preql_savepoint", ())-        Transaction . ExceptT . ReaderT $ \conn -> do-            e_a <- runReaderT (runExceptT t) conn-            case e_a of-                Right _ -> void $ W.query_ conn (Query "RELEASE SAVEPOINT preql_savepoint") ()-                Left _ -> do-                    void $ W.query_ conn (Query "ROLLBACK SAVEPOINT preql_savepoint") ()-                    -- release after rollback, so a later rolback catches the outer same-named savepoint-                    void $ W.query_ conn (Query "RELEASE SAVEPOINT preql_savepoint") ()-            return e_a
src/Preql/QuasiQuoter/Raw/TH.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE DisambiguateRecordFields #-} {-# LANGUAGE DuplicateRecordFields    #-} {-# LANGUAGE NamedFieldPuns           #-}@@ -60,22 +61,22 @@             query <- makeQuery rewritten             case positionalCount of                 0 -> -- only antiquotes (or no params)-                    return $ TupE [query, tupleOrSingle antiNames]+                    return $ tupleE [query, tupleOrSingle antiNames]                 1 -> do -- one positional param, doesn't take a tuple                     patternName <- newName "c"                     return $ LamE [VarP patternName]-                        (TupE [query, tupleOrSingle (patternName : antiNames)])+                        (tupleE [query, tupleOrSingle (patternName : antiNames)])                 _ -> do -- at least two positional parameters                     patternNames <- cNames 'q' (fromIntegral positionalCount)                     return $ LamE                         [TupP (map VarP patternNames)]-                        (TupE [query, tupleOrSingle (patternNames ++ antiNames)])+                        (tupleE [query, tupleOrSingle (patternNames ++ antiNames)])         Left err -> error err  tupleOrSingle :: [Name] -> Exp tupleOrSingle names = case names of     [name] -> VarE name-    vs -> TupE $ map VarE vs+    vs -> tupleE $ map VarE vs  expressionOnly :: String -> (String -> Q Exp) -> QuasiQuoter expressionOnly name qq = QuasiQuoter@@ -104,3 +105,10 @@               in (unLex (NumberedParam newParam) : ss, name : ns)           EOF -> go maxParam ts           _ -> let (ss, ns) = go maxParam ts in (unLex token : ss, ns)++tupleE :: [Exp] -> Exp+#if MIN_VERSION_template_haskell(2,16,0)+tupleE = TupE . fmap Just+#else+tupleE = TupE+#endif
src/Preql/Wire.hs view
@@ -16,3 +16,4 @@ import Preql.Wire.Internal as X (Query, RowDecoder) import Preql.Wire.ToSql as X import Preql.Wire.Types as X+import Preql.Wire.Query as X (IsolationLevel(..))
src/Preql/Wire/Errors.hs view
@@ -27,8 +27,13 @@ instance Exception FieldError $(deriveJSON defaultOptions ''FieldError) +data PgType = Oid PQ.Oid -- ^ A Postgres type with a known ID+    | TypeName Text -- ^ A Postgres type which we will need to lookup by name+    deriving (Eq, Show, Typeable)+$(deriveJSON defaultOptions ''PgType)+ data TypeMismatch = TypeMismatch-    { expected :: PQ.Oid+    { expected :: PgType     , actual :: PQ.Oid     , column :: Int     , columnName :: Maybe Text
src/Preql/Wire/FromSql.hs view
@@ -35,7 +35,7 @@ -- | A @FieldDecoder@ for a type @a@ consists of an OID indicating the -- Postgres type which can be decoded, and a parser from the binary -- representation of that type to the Haskell representation.-data FieldDecoder a = FieldDecoder PQ.Oid (BP.BinaryParser a)+data FieldDecoder a = FieldDecoder PgType (BP.BinaryParser a)     deriving Functor  throwLocated :: UnlocatedFieldError -> InternalDecoder a@@ -43,13 +43,14 @@     DecoderState{row = PQ.Row r, column = PQ.Col c} <- get     throwError (FieldError (fromIntegral r) (fromIntegral c) failure) -decodeVector :: RowDecoder a -> PQ.Result -> IO (Either QueryError (Vector a))-decodeVector rd@(RowDecoder oids _parsers) result = do-    mismatches <- fmap catMaybes $ for (zip [0 ..] oids) $ \(column@(PQ.Col cint), expected) -> do-        actual <- liftIO $ PQ.ftype result column-        if actual == expected-            then return Nothing-            else do+decodeVector :: (PgType -> IO (Either QueryError PQ.Oid)) -> RowDecoder a -> PQ.Result -> IO (Either QueryError (Vector a))+decodeVector lookupType rd@(RowDecoder pgtypes _parsers) result = do+    mismatches <- fmap catMaybes $ for (zip [0 ..] pgtypes) $ \(column@(PQ.Col cint), expected) -> do+        actual <- PQ.ftype result column+        e_expectedOid <- lookupType expected+        case e_expectedOid of+            Right oid | actual == oid -> return Nothing+            _ -> do                 m_name <- liftIO $ PQ.fname result column                 let columnName = decodeUtf8With lenientDecode <$> m_name                 return $ Just (TypeMismatch{column = fromIntegral cint, ..})@@ -82,44 +83,44 @@     fromSql :: RowDecoder a  instance FromSqlField Bool where-    fromSqlField = FieldDecoder OID.boolOid PGB.bool+    fromSqlField = FieldDecoder (Oid OID.boolOid) PGB.bool instance FromSql Bool where fromSql = notNull fromSqlField  instance FromSqlField Int16 where-    fromSqlField = FieldDecoder OID.int2Oid PGB.int+    fromSqlField = FieldDecoder (Oid OID.int2Oid) PGB.int instance FromSql Int16 where fromSql = notNull fromSqlField  instance FromSqlField Int32 where-    fromSqlField = FieldDecoder OID.int4Oid PGB.int+    fromSqlField = FieldDecoder (Oid OID.int4Oid) PGB.int instance FromSql Int32 where fromSql = notNull fromSqlField  instance FromSqlField Int64  where-    fromSqlField = FieldDecoder OID.int8Oid PGB.int+    fromSqlField = FieldDecoder (Oid OID.int8Oid) PGB.int instance FromSql Int64 where fromSql = notNull fromSqlField  instance FromSqlField Float where-    fromSqlField = FieldDecoder OID.float4Oid PGB.float4+    fromSqlField = FieldDecoder (Oid OID.float4Oid) PGB.float4 instance FromSql Float where fromSql = notNull fromSqlField  instance FromSqlField Double where-    fromSqlField = FieldDecoder OID.float8Oid PGB.float8+    fromSqlField = FieldDecoder (Oid OID.float8Oid) PGB.float8 instance FromSql Double where fromSql = notNull fromSqlField  -- TODO does Postgres have a single-char type?  Does it always return bpchar? -- instance FromSqlField Char where---     fromSqlField = FieldDecoder OID.charOid PGB.char+--     fromSqlField = FieldDecoder (Oid OID.charOid) PGB.char -- instance FromSql Char where fromSql = notNull fromSqlField  instance FromSqlField String where-    fromSqlField = FieldDecoder OID.textOid (T.unpack <$> PGB.text_strict)+    fromSqlField = FieldDecoder (Oid OID.textOid) (T.unpack <$> PGB.text_strict) instance FromSql String where fromSql = notNull fromSqlField  instance FromSqlField Text where-    fromSqlField = FieldDecoder OID.textOid PGB.text_strict+    fromSqlField = FieldDecoder (Oid OID.textOid) PGB.text_strict instance FromSql Text where fromSql = notNull fromSqlField  instance FromSqlField TL.Text where-    fromSqlField = FieldDecoder OID.textOid PGB.text_lazy+    fromSqlField = FieldDecoder (Oid OID.textOid) PGB.text_lazy instance FromSql TL.Text where fromSql = notNull fromSqlField  -- | If you want to encode some more specific Haskell type via JSON,@@ -127,43 +128,47 @@ -- 'PostgreSQL.Binary.Encoding.jsonb_bytes' directly, rather than this -- instance. instance FromSqlField ByteString where-    fromSqlField = FieldDecoder OID.byteaOid (BS.copy <$> BP.remainders)+    fromSqlField = FieldDecoder (Oid OID.byteaOid) (BS.copy <$> BP.remainders) instance FromSql ByteString where fromSql = notNull fromSqlField  instance FromSqlField BSL.ByteString where-    fromSqlField = FieldDecoder OID.byteaOid (BSL.fromStrict . BS.copy <$> BP.remainders)+    fromSqlField = FieldDecoder (Oid OID.byteaOid) (BSL.fromStrict . BS.copy <$> BP.remainders) instance FromSql BSL.ByteString where fromSql = notNull fromSqlField  -- TODO check for integer_datetimes setting instance FromSqlField UTCTime where-    fromSqlField = FieldDecoder OID.timestamptzOid PGB.timestamptz_int+    fromSqlField = FieldDecoder (Oid OID.timestamptzOid) PGB.timestamptz_int instance FromSql UTCTime where fromSql = notNull fromSqlField  instance FromSqlField Day where-    fromSqlField = FieldDecoder OID.dateOid PGB.date+    fromSqlField = FieldDecoder (Oid OID.dateOid) PGB.date instance FromSql Day where fromSql = notNull fromSqlField  instance FromSqlField TimeOfDay where-    fromSqlField = FieldDecoder OID.timeOid PGB.time_int+    fromSqlField = FieldDecoder (Oid OID.timeOid) PGB.time_int instance FromSql TimeOfDay where fromSql = notNull fromSqlField  instance FromSqlField TimeTZ where-    fromSqlField = FieldDecoder OID.timetzOid (uncurry TimeTZ <$> PGB.timetz_int)+    fromSqlField = FieldDecoder (Oid OID.timetzOid) (uncurry TimeTZ <$> PGB.timetz_int) instance FromSql TimeTZ where fromSql = notNull fromSqlField  instance FromSqlField UUID where-    fromSqlField = FieldDecoder OID.uuidOid PGB.uuid+    fromSqlField = FieldDecoder (Oid OID.uuidOid) PGB.uuid instance FromSql UUID where fromSql = notNull fromSqlField +instance FromSqlField PQ.Oid where+    fromSqlField = PQ.Oid <$> FieldDecoder (Oid OID.oidOid) PGB.int+instance FromSql PQ.Oid where fromSql = notNull fromSqlField+ -- | If you want to encode some more specific Haskell type via JSON, -- it is more efficient to use 'fromSqlJsonField' rather than this -- instance. instance FromSqlField JSON.Value where-    fromSqlField = FieldDecoder OID.jsonbOid PGB.jsonb_ast+    fromSqlField = FieldDecoder (Oid OID.jsonbOid) PGB.jsonb_ast instance FromSql JSON.Value where fromSql = notNull fromSqlField  fromSqlJsonField :: JSON.FromJSON a => FieldDecoder a-fromSqlJsonField = FieldDecoder OID.jsonbOid+fromSqlJsonField = FieldDecoder (Oid OID.jsonbOid)     (PGB.jsonb_bytes (first T.pack . JSON.eitherDecode . BSL.fromStrict))  -- Overlappable so applications can write Maybe for multi-field domain types
src/Preql/Wire/Internal.hs view
@@ -25,11 +25,10 @@ newtype Query = Query ByteString     deriving (Show, IsString) --- TODO PgType for non-builtin types -- | @RowDecoder@ is 'Applicative' but not 'Monad' so that we can -- assemble all of the OIDs before we read any of the field data sent--- by Postgresj.-data RowDecoder a = RowDecoder [PQ.Oid] (InternalDecoder a)+-- by Postgres.+data RowDecoder a = RowDecoder [PgType] (InternalDecoder a)     deriving Functor  instance Applicative RowDecoder where@@ -37,6 +36,9 @@     RowDecoder t1 p1 <*> RowDecoder t2 p2 = RowDecoder (t1 <> t2) (p1 <*> p2)  -- TODO can I use ValidationT instead of ExceptT, since I ensure Column is incremented before errors?+-- | Internal because we need IO for the libpq FFI, but we promise not+-- to do any IO besides decoding.  We don't even make network calls to+-- Postgres in @InternalDecoder@ type InternalDecoder =  StateT DecoderState (ExceptT FieldError IO)  data DecoderState = DecoderState
src/Preql/Wire/Query.hs view
@@ -9,6 +9,7 @@ import Preql.Imports  import qualified Data.Text as T+import qualified Data.Vector as V import qualified Database.PostgreSQL.LibPQ as PQ  queryWith :: RowEncoder p -> RowDecoder r -> PQ.Connection -> Query -> p -> IO (Either QueryError (Vector r))@@ -18,7 +19,7 @@         e_result <- execParams enc conn query params         case e_result of             Left err -> return (Left err)-            Right result -> decodeVector dec result+            Right result -> decodeVector (lookupType conn) dec result  -- If there is no result, we don't need a Decoder queryWith_ :: RowEncoder p -> PQ.Connection -> Query -> p -> IO (Either QueryError ())@@ -53,3 +54,31 @@     case m_msg of         Just msg -> return (Left (decodeUtf8With lenientDecode msg))         Nothing -> return (Left "No error message available")++lookupType :: PQ.Connection -> PgType -> IO (Either QueryError PQ.Oid)+lookupType _ (Oid oid) = return (Right oid)+lookupType conn (TypeName name) = do+    e_rows <- query conn "SELECT oid FROM pg_type WHERE typname = $1" name+    case fmap (V.!? 0) e_rows of+        Left e -> return (Left e)+        Right (Just oid) -> return (Right oid)+        Right Nothing -> return (Left (ConnectionError ("No oid for: " <> name)))++data IsolationLevel+    = ReadCommitted+    | RepeatableRead+    | Serializable+    deriving (Show, Read, Eq, Ord, Enum, Bounded)++begin :: PQ.Connection -> IsolationLevel -> IO (Either QueryError ())+begin conn level = query_ conn q () where+  q = case level of+    ReadCommitted  -> "BEGIN ISOLATION LEVEL READ COMMITTED"+    RepeatableRead -> "BEGIN ISOLATION LEVEL REPEATABLE READ"+    Serializable   -> "BEGIN ISOLATION LEVEL SERIALIZABLE"++commit :: PQ.Connection -> IO (Either QueryError ())+commit conn = query_ conn "COMMIT" ()++rollback :: PQ.Connection -> IO (Either QueryError ())+rollback conn = query_ conn "ROLLBACK" ()
test/Test/Wire.hs view
@@ -1,15 +1,13 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} module Test.Wire where -import Preql.Effect-import Preql.QuasiQuoter.Raw.TH (sql) import Preql.Wire+import Test.Wire.Enum  import Control.Exception (Exception, bracket_, throwIO) import Control.Monad@@ -18,10 +16,11 @@ import Data.Int import Data.Maybe (fromMaybe) import Data.Text (Text)-import Data.Time (Day, TimeOfDay, UTCTime, TimeZone) #if MIN_VERSION_time(1,9,0)+import Data.Time (Day, TimeOfDay, UTCTime) import Data.Time.Format.ISO8601 (iso8601ParseM) #else+import Data.Time (Day, TimeOfDay, UTCTime, TimeZone) import Data.Time.Format (parseTimeM, defaultTimeLocale, iso8601DateFormat, ParseTime) #endif import Data.Text.Encoding (encodeUtf8)@@ -174,6 +173,19 @@                 query_ "INSERT INTO encoder_tests (b, i16, i32, i64) VALUES ($1, $2, $3, $4)" v                 assertQuery [v] "SELECT b, i16, i32, i64 FROM encoder_tests"             ]++        , testGroup "user-defined types"+          [ inTransaction "decode enum" $ do+                  query_ "drop type if exists my_enum" ()+                  query_ "create type my_enum as enum  ('A', 'B', 'C')" ()+                  assertQuery [A] "select 'A'::my_enum"+          , inTransaction "type mismatch error" $ do+                  mismatch <- query @() @MyEnum "select 'A'" ()+                  case mismatch of+                      Left (PgTypeMismatch+                             [TypeMismatch { expected = TypeName "my_enum", column = 0 }]) -> pure ()+                      _ -> assertFailure "did not get expected TypeMismatch error"+          ]         ]  initDB :: HasCallStack => IO PQ.Connection
+ test/Test/Wire/Enum.hs view
@@ -0,0 +1,21 @@+-- | An Enum type to test Postgres typname lookup.++module Test.Wire.Enum where++import Preql.Wire++import Text.Read (readMaybe)+import qualified BinaryParser as BP+import qualified Data.Text as T+import qualified PostgreSQL.Binary.Decoding as PGB++data MyEnum = A | B | C+    deriving (Show, Read, Eq, Ord, Bounded, Enum)++instance FromSqlField MyEnum where+    fromSqlField = FieldDecoder (TypeName "my_enum") $ do+        text <- PGB.text_strict+        case readMaybe (T.unpack text) of+            Just x -> return x+            Nothing -> BP.failure ("could not parse as MyEnum: " <> text)+instance FromSql MyEnum where fromSql = notNull fromSqlField