-
Notifications
You must be signed in to change notification settings - Fork 43
Add support for prepared statements #369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| ### Removed | ||
|
|
||
| - Removed support for `network-ip`. We still support `iproute`. | ||
|
|
||
| ### Added | ||
|
|
||
| - Add support for prepared statements. To use prepared statements, simply use `prepare run` instead of `run` with a function that passes the parameters to your statement. | ||
|
|
||
| - Added new `Encoder` type with three members: `binary`, which is the Hasql binary encoder, `text` which encodes a type in PostgreSQL's text format (needed for nested arrays) and `quote`, which is the does the thing that the function we previously called `encode` does (i.e., `a -> Opaleye.PrimExpr`). | ||
|
|
||
| ### Changed | ||
|
|
||
| - Several changes to `TypeInformation`: | ||
|
|
||
| * Changed the `encode` field of `TypeInformation` to be `Encoder a` instead of `a -> Opaleye.PrimExpr`. | ||
|
|
||
| * Moved the `delimiter` field of `Decoder` into the top level of `TypeInformation`, as it's not "decoding" specific, it's also used when "encoding". | ||
|
|
||
| * Renamed the `parser` field of `Decoder` to `text`, to mirror the `text` field of the new `Encoder` type. | ||
|
|
||
| All of this will break any downstream code that uses a completely custom `DBType` implementation, but anything that uses `ReadShow`, `Enum`, `Composite`, `JSONBEncoded` or `parseTypeInformation` will continue working as before (which should cover all common cases). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| {-# language AllowAmbiguousTypes #-} | ||
| {-# language BlockArguments #-} | ||
| {-# language FlexibleContexts #-} | ||
| {-# language MonoLocalBinds #-} | ||
| {-# language NamedFieldPuns #-} | ||
| {-# language ScopedTypeVariables #-} | ||
| {-# language TypeApplications #-} | ||
|
|
||
| module Rel8.Statement.Prepared ( | ||
| input, | ||
| prepared, | ||
| ) where | ||
|
|
||
| -- base | ||
| import Data.Functor.Const (Const (Const), getConst) | ||
| import Data.Functor.Contravariant (contramap, (>$<)) | ||
| import Data.Functor.Identity (runIdentity) | ||
| import Prelude | ||
|
|
||
| -- hasql | ||
| import qualified Hasql.Encoders as Hasql | ||
| import qualified Hasql.Statement as Hasql | ||
|
|
||
| -- opaleye | ||
| import qualified Opaleye.Internal.HaskellDB.PrimQuery as Opaleye | ||
|
|
||
| -- rel8 | ||
| import Rel8.Expr (Expr) | ||
| import Rel8.Expr.Opaleye (fromPrimExpr, scastExpr) | ||
| import Rel8.Schema.HTable (hfield, hspecs, htabulateA) | ||
| import Rel8.Schema.Null (Nullity (Null, NotNull)) | ||
| import Rel8.Schema.Spec (Spec (..)) | ||
| import Rel8.Statement (Statement) | ||
| import Rel8.Table (Table, fromColumns, toResult) | ||
| import Rel8.Table.Serialize (Serializable) | ||
| import Rel8.Type.Encoder (binary) | ||
| import Rel8.Type.Information (encode) | ||
|
|
||
| -- transformers | ||
| import Control.Monad.Trans.State.Strict (evalState, state) | ||
|
|
||
|
|
||
| {-| Given a 'Rel8.run' function that converts a 'Statement' to a | ||
| 'Hasql.Statement', return a 'Rel8.run'-like function which instead takes a | ||
| /parameterized/ 'Statement' and converts it to a /preparable/ | ||
| 'Hasql.Statement'. | ||
|
|
||
| The parameters @i@ are sent to the database directly via PostgreSQL's binary | ||
| format. For large amounts of data this can be significantly more efficient | ||
| than embedding the values in the statement with 'Rel8.lit'. | ||
| -} | ||
| prepared :: forall a b i o. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seeems more like
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused about what you mean by "calling a prepared statement". The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, I am probably confused! |
||
| Serializable a i => | ||
| (Statement b -> Hasql.Statement () o) -> | ||
| (a -> Statement b) -> | ||
| Hasql.Statement i o | ||
| prepared run mkStatement = Hasql.Statement sql (encoder @a) decode True | ||
| where | ||
| Hasql.Statement sql _ decode _ = run $ mkStatement input | ||
|
|
||
|
|
||
| encoder :: forall a i. Serializable a i => Hasql.Params i | ||
| encoder = | ||
| contramap (toResult @_ @a) $ | ||
| getConst $ | ||
| htabulateA \field -> | ||
| case hfield hspecs field of | ||
| Spec {nullity, info} -> Const $ | ||
| runIdentity . (`hfield` field) >$< | ||
| case nullity of | ||
| Null -> Hasql.param $ Hasql.nullable build | ||
| NotNull -> Hasql.param $ Hasql.nonNullable build | ||
| where | ||
| build = binary (encode info) | ||
|
|
||
|
|
||
| input :: Table Expr a => a | ||
| input = | ||
| fromColumns $ | ||
| flip (evalState @Word) 1 do | ||
| htabulateA \field -> do | ||
| n <- state (\n -> (n, n + 1)) | ||
| pure | ||
| case hfield hspecs field of | ||
| Spec {info} -> | ||
| scastExpr info $ fromPrimExpr $ | ||
| Opaleye.ConstExpr $ Opaleye.OtherLit $ '$' : show n | ||
Uh oh!
There was an error while loading. Please reload this page.