packages feed

hasql-dynamic-statements 0.4 → 0.5

raw patch · 8 files changed

+170/−150 lines, 8 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Hasql.DynamicStatements.Session: dynamicallyParameterizedStatement :: Snippet -> Result result -> Session result
- Hasql.DynamicStatements.Statement: dynamicallyParameterized :: Snippet -> Result result -> Statement () result
+ Hasql.DynamicStatements.Snippet: instance GHC.Internal.Base.Monoid Hasql.DynamicStatements.Snippet.Snippet
+ Hasql.DynamicStatements.Snippet: instance GHC.Internal.Base.Semigroup Hasql.DynamicStatements.Snippet.Snippet
+ Hasql.DynamicStatements.Snippet: instance GHC.Internal.Data.String.IsString Hasql.DynamicStatements.Snippet.Snippet
+ Hasql.DynamicStatements.Snippet: toPipeline :: Snippet -> Result result -> Pipeline result
+ Hasql.DynamicStatements.Snippet: toSession :: Snippet -> Result result -> Session result
+ Hasql.DynamicStatements.Snippet: toSql :: Snippet -> Text
+ Hasql.DynamicStatements.Snippet: toStatement :: Snippet -> Result result -> Statement () result

Files

CHANGELOG.md view
@@ -1,3 +1,9 @@+# v0.5++- (Breaking) Removed `Hasql.DynamicStatements.Session` module+- (Breaking) Removed `Hasql.DynamicStatements.Statement` module+- Added `toStatement`, `toSession`, `toPipeline` functions to the `Hasql.DynamicStatements.Snippet` module instead+ # v0.4  - Migrated to Hasql 1.10
hasql-dynamic-statements.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: hasql-dynamic-statements-version: 0.4+version: 0.5 synopsis: Hasql extension for dynamic construction of statements description:   Library extending Hasql with an abstraction that simplifies construction of SQL statements, the structure of which depends on the parameters.@@ -81,13 +81,10 @@   import: base   hs-source-dirs: src/library   exposed-modules:-    Hasql.DynamicStatements.Session     Hasql.DynamicStatements.Snippet-    Hasql.DynamicStatements.Statement    other-modules:     Hasql.DynamicStatements.Prelude-    Hasql.DynamicStatements.Snippet.Defs    build-depends:     base >=4.14 && <5,
− src/library/Hasql/DynamicStatements/Session.hs
@@ -1,18 +0,0 @@-module Hasql.DynamicStatements.Session where--import Hasql.Decoders qualified as Decoders-import Hasql.DynamicStatements.Snippet.Defs qualified as SnippetDefs-import Hasql.DynamicStatements.Statement qualified as Statement-import Hasql.Session-import Hasql.Session qualified as Session---- |--- Execute a dynamically parameterized statement, providing a result decoder.------ This is merely a shortcut, which immediately embeds--- @Hasql.DynamicStatements.Statement.'Statement.dynamicallyParameterized'@--- in @Session@.--- For details see the docs on that function.-dynamicallyParameterizedStatement :: SnippetDefs.Snippet -> Decoders.Result result -> Session result-dynamicallyParameterizedStatement snippet decoder =-  Session.statement () (Statement.dynamicallyParameterized snippet decoder)
src/library/Hasql/DynamicStatements/Snippet.hs view
@@ -1,9 +1,167 @@ module Hasql.DynamicStatements.Snippet   ( Snippet,++    -- * Execution+    toSql,+    toStatement,+    toSession,+    toPipeline,++    -- * Construction     param,     encoderAndParam,     sql,   ) where -import Hasql.DynamicStatements.Snippet.Defs+import Hasql.Decoders qualified as Decoders+import Hasql.DynamicStatements.Prelude+import Hasql.Encoders qualified as Encoders+import Hasql.Implicits.Encoders qualified as Encoders+import Hasql.Pipeline qualified as Pipeline+import Hasql.Session qualified as Session+import Hasql.Statement qualified as Statement+import TextBuilder qualified++-- |+-- Composable SQL snippet with parameters injected.+-- Abstracts over placeholders and matching of encoders.+--+-- It has an instance of `IsString`, so having the @OverloadedStrings@ extension enabled+-- you can construct it directly from string literals.+--+-- Here's an example:+--+-- @+-- selectSubstring :: Text -> Maybe Int32 -> Maybe Int32 -> 'Snippet'+-- selectSubstring string from to =+--   "select substring(" <> 'param' string <>+--   'foldMap' (\\ x -> " from " <> 'param' x) from <>+--   'foldMap' (\\ x -> " for " <> 'param' x) to <>+--   ")"+-- @+--+-- Having a decoder you can lift it into 'Hasql.Statement.Statement' using+-- 'Hasql.DynamicStatements.Snippet.toStatement' or directly execute it in+-- 'Hasql.Session.Session' using+-- 'Hasql.DynamicStatements.Snippet.toSession'.+newtype Snippet = Snippet (Seq SnippetChunk)++data SnippetChunk+  = StringSnippetChunk TextBuilder.TextBuilder+  | ParamSnippetChunk (Encoders.Params ())++deriving instance Semigroup Snippet++deriving instance Monoid Snippet++instance IsString Snippet where+  fromString x = Snippet (pure (StringSnippetChunk (fromString x)))++-- |+-- SQL chunk.+sql :: Text -> Snippet+sql x = Snippet (pure (StringSnippetChunk (TextBuilder.text x)))++-- |+-- Parameter encoded using an implicitly derived encoder from the type.+param :: (Encoders.DefaultParamEncoder param) => param -> Snippet+param = encoderAndParam Encoders.defaultParam++-- |+-- Parameter with an explicitly defined encoder.+encoderAndParam :: Encoders.NullableOrNot Encoders.Value param -> param -> Snippet+encoderAndParam encoder param = Snippet (pure (ParamSnippetChunk (param >$ Encoders.param encoder)))++compile :: Snippet -> (Text, Encoders.Params ())+compile (Snippet chunks) =+  let step (!paramId, !builder, !encoder) = \case+        StringSnippetChunk sql ->+          (paramId, builder <> sql, encoder)+        ParamSnippetChunk paramEncoder ->+          let newParamId = paramId + 1+              newPoking = builder <> "$" <> TextBuilder.decimal paramId+              newEncoder = encoder <> paramEncoder+           in (newParamId, newPoking, newEncoder)+      (_, builder, encoder) = foldl' step (1 :: Int, mempty, mempty) chunks+      sql = TextBuilder.toText builder+   in (sql, encoder)++-- |+-- Compile a snippet into SQL text with placeholders.+toSql :: Snippet -> Text+toSql snippet = fst (compile snippet)++-- |+-- Construct a statement dynamically, specifying the parameters in-place+-- in the declaration of snippet and providing a result decoder and+-- specifying whether the statement should be prepared.+--+-- The injection of the parameters is handled automatically,+-- generating parametric statements with binary encoders under the hood.+--+-- This is useful when the SQL of your statement depends on the parameters.+-- Here's an example:+--+-- @+-- selectSubstring :: Text -> Maybe Int32 -> Maybe Int32 -> 'Statement' () Text+-- selectSubstring string from to = let+--   snippet =+--     "select substring(" <> Snippet.'param' string <>+--     foldMap (mappend " from " . Snippet.'param') from <>+--     foldMap (mappend " for " . Snippet.'param') to <>+--     ")"+--   decoder = Decoders.'Decoders.singleRow' (Decoders.'Decoders.column' (Decoders.'Decoders.nonNullable' Decoders.'Decoders.text'))+--   in 'toStatement' snippet decoder+-- @+--+-- Without the Snippet API you would have had to implement the same functionality thus:+--+-- @+-- selectSubstring' :: Text -> Maybe Int32 -> Maybe Int32 -> 'Statement' () Text+-- selectSubstring' string from to = let+--   sql = case (from, to) of+--     (Just _, Just _) -> "select substring($1 from $2 to $3)"+--     (Just _, Nothing) -> "select substring($1 from $2)"+--     (Nothing, Just _) -> "select substring($1 to $2)"+--     (Nothing, Nothing) -> "select substring($1)"+--   encoder =+--     Encoders.'Encoders.param' (string '>$' Encoders.'Encoders.text') '<>'+--     foldMap (\\ x -> Encoders.'Encoders.param' (x '>$' Encoders.'Encoders.int8')) from '<>'+--     foldMap (\\ x -> Encoders.'Encoders.param' (x '>$' Encoders.'Encoders.int8')) to+--   decoder = Decoders.'Decoders.singleRow' (Decoders.'Decoders.column' (Decoders.'Decoders.nonNullable' Decoders.'Decoders.text'))+--   in Statement.'Statement.preparable' sql encoder decoder+-- @+--+-- As you can see, the Snippet API abstracts over placeholders and+-- matching encoder generation, thus also protecting you from all sorts of related bugs.+toStatement :: Snippet -> Decoders.Result result -> Statement.Statement () result+toStatement snippet =+  let (sql, encoder) = compile snippet+   in Statement.unpreparable sql encoder++-- |+-- Execute in @Session.Session@, providing a result decoder.+--+-- This is merely a shortcut defined thus:+--+-- @+-- toSession snippet decoder =+--   Session.statement () (Snippet.toStatement snippet decoder)+-- @+toSession :: Snippet -> Decoders.Result result -> Session.Session result+toSession snippet decoder =+  Session.statement () (toStatement snippet decoder)++-- |+-- Execute in @Pipeline.Pipeline@, providing a result decoder.+--+-- This is merely a shortcut defined thus:+--+-- @+-- toPipeline snippet decoder =+--   Pipeline.statement () (Snippet.toStatement snippet decoder)+-- @+toPipeline :: Snippet -> Decoders.Result result -> Pipeline.Pipeline result+toPipeline snippet decoder =+  Pipeline.statement () (toStatement snippet decoder)
− src/library/Hasql/DynamicStatements/Snippet/Defs.hs
@@ -1,70 +0,0 @@-module Hasql.DynamicStatements.Snippet.Defs where--import Hasql.DynamicStatements.Prelude-import Hasql.Encoders qualified as Encoders-import Hasql.Implicits.Encoders qualified as Encoders-import TextBuilder qualified---- |--- Composable SQL snippet with parameters injected.--- Abstracts over placeholders and matching of encoders.------ It has an instance of `IsString`, so having the @OverloadedStrings@ extension enabled--- you can construct it directly from string literals.------ Here's an example:------ @--- selectSubstring :: Text -> Maybe Int32 -> Maybe Int32 -> 'Snippet'--- selectSubstring string from to =---   "select substring(" <> 'param' string <>---   'foldMap' (\\ x -> " from " <> 'param' x) from <>---   'foldMap' (\\ x -> " for " <> 'param' x) to <>---   ")"--- @------ Having a decoder you can lift it into 'Hasql.Statement.Statement' using--- 'Hasql.DynamicStatements.Statement.dynamicallyParameterized' or directly execute it in--- 'Hasql.Session.Session' using--- 'Hasql.DynamicStatements.Session.dynamicallyParameterizedStatement'.-newtype Snippet = Snippet (Seq SnippetChunk)--data SnippetChunk-  = StringSnippetChunk TextBuilder.TextBuilder-  | ParamSnippetChunk (Encoders.Params ())--deriving instance Semigroup Snippet--deriving instance Monoid Snippet--instance IsString Snippet where-  fromString x = Snippet (pure (StringSnippetChunk (fromString x)))---- |--- SQL chunk.-sql :: Text -> Snippet-sql x = Snippet (pure (StringSnippetChunk (TextBuilder.text x)))---- |--- Parameter encoded using an implicitly derived encoder from the type.-param :: (Encoders.DefaultParamEncoder param) => param -> Snippet-param = encoderAndParam Encoders.defaultParam---- |--- Parameter with an explicitly defined encoder.-encoderAndParam :: Encoders.NullableOrNot Encoders.Value param -> param -> Snippet-encoderAndParam encoder param = Snippet (pure (ParamSnippetChunk (param >$ Encoders.param encoder)))--compile :: Snippet -> (Text, Encoders.Params ())-compile (Snippet chunks) =-  let step (!paramId, !builder, !encoder) = \case-        StringSnippetChunk sql ->-          (paramId, builder <> sql, encoder)-        ParamSnippetChunk paramEncoder ->-          let newParamId = paramId + 1-              newPoking = builder <> "$" <> TextBuilder.decimal paramId-              newEncoder = encoder <> paramEncoder-           in (newParamId, newPoking, newEncoder)-      (_, builder, encoder) = foldl' step (1 :: Int, mempty, mempty) chunks-      sql = TextBuilder.toText builder-   in (sql, encoder)
− src/library/Hasql/DynamicStatements/Statement.hs
@@ -1,53 +0,0 @@-module Hasql.DynamicStatements.Statement where--import Hasql.Decoders qualified as Decoders-import Hasql.DynamicStatements.Snippet.Defs qualified as SnippetDefs-import Hasql.Statement---- |--- Construct a statement dynamically, specifying the parameters in-place--- in the declaration of snippet and providing a result decoder and--- specifying whether the statement should be prepared.------ The injection of the parameters is handled automatically,--- generating parametric statements with binary encoders under the hood.------ This is useful when the SQL of your statement depends on the parameters.--- Here's an example:------ @--- selectSubstring :: Text -> Maybe Int32 -> Maybe Int32 -> 'Statement' () Text--- selectSubstring string from to = let---   snippet =---     "select substring(" <> Snippet.'SnippetDefs.param' string <>---     foldMap (mappend " from " . Snippet.'SnippetDefs.param') from <>---     foldMap (mappend " for " . Snippet.'SnippetDefs.param') to <>---     ")"---   decoder = Decoders.'Decoders.singleRow' (Decoders.'Decoders.column' (Decoders.'Decoders.nonNullable' Decoders.'Decoders.text'))---   in 'dynamicallyParameterized' snippet decoder--- @------ Without the Snippet API you would have had to implement the same functionality thus:------ @--- selectSubstring' :: Text -> Maybe Int32 -> Maybe Int32 -> 'Statement' () Text--- selectSubstring' string from to = let---   sql = case (from, to) of---     (Just _, Just _) -> "select substring($1 from $2 to $3)"---     (Just _, Nothing) -> "select substring($1 from $2)"---     (Nothing, Just _) -> "select substring($1 to $2)"---     (Nothing, Nothing) -> "select substring($1)"---   encoder =---     Encoders.'Encoders.param' (string '>$' Encoders.'Encoders.text') '<>'---     foldMap (\\ x -> Encoders.'Encoders.param' (x '>$' Encoders.'Encoders.int8')) from '<>'---     foldMap (\\ x -> Encoders.'Encoders.param' (x '>$' Encoders.'Encoders.int8')) to---   decoder = Decoders.'Decoders.singleRow' (Decoders.'Decoders.column' (Decoders.'Decoders.nonNullable' Decoders.'Decoders.text'))---   in Statement.'Statement.preparable' sql encoder decoder--- @------ As you can see, the Snippet API abstracts over placeholders and--- matching encoder generation, thus also protecting you from all sorts of related bugs.-dynamicallyParameterized :: SnippetDefs.Snippet -> Decoders.Result result -> Statement () result-dynamicallyParameterized snippet decoder =-  let (sql, encoder) = SnippetDefs.compile snippet-   in unpreparable sql encoder decoder
src/test/Regressions/Issue2Spec.hs view
@@ -2,8 +2,8 @@  import Hasql.Connection qualified as Connection import Hasql.Decoders qualified as Decoders-import Hasql.DynamicStatements.Session qualified as Session import Hasql.DynamicStatements.Snippet qualified as Snippet+import Hasql.Session qualified as Session import Test.Hspec import Prelude @@ -13,7 +13,7 @@     it "Doesn't happen" \connection -> do       let snippet =             "SELECT 1 " <> (foldMap @[] ("," <>) (replicate 1001 (Snippet.param (10 :: Int64))))-      result <- Connection.use connection (Session.dynamicallyParameterizedStatement snippet Decoders.noResult)+      result <- Connection.use connection (Session.statement () (Snippet.toStatement snippet Decoders.noResult))       case result of         Right () -> pure ()         _ -> expectationFailure "Statement execution failed"
src/test/Units/SnippetSpec.hs view
@@ -2,8 +2,8 @@  import Hasql.Connection qualified as Connection import Hasql.Decoders qualified as Decoders-import Hasql.DynamicStatements.Session qualified as Session import Hasql.DynamicStatements.Snippet qualified as Snippet+import Hasql.Session qualified as Session import Test.Hspec import Prelude @@ -19,7 +19,7 @@                     <> foldMap (mappend " for " . Snippet.param @Int32) to                     <> ")"                 decoder = Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.text))-             in Connection.use connection (Session.dynamicallyParameterizedStatement snippet decoder)+             in Connection.use connection (Session.statement () (Snippet.toStatement snippet decoder))        in do             shouldBe (Right "bc") =<< sample "abcd" (Just 2) (Just 2)             shouldBe (Right "bcd") =<< sample "abcd" (Just 2) (Just 3)