packages feed

hasql-dynamic-statements 0.5 → 0.5.0.1

raw patch · 3 files changed

+120/−30 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

+ README.md view
@@ -0,0 +1,72 @@+# hasql-dynamic-statements++[![Hackage](https://img.shields.io/hackage/v/hasql-dynamic-statements.svg)](https://hackage.haskell.org/package/hasql-dynamic-statements)+[![Continuous Haddock](https://img.shields.io/badge/haddock-master-blue)](https://nikita-volkov.github.io/hasql-dynamic-statements/)++Hasql extension for composable, dynamic construction of SQL statements.++## Overview++This library provides a **Snippet** API that simplifies building SQL statements where the structure depends on runtime parameters. It abstracts over placeholder management and encoder matching, eliminating boilerplate and reducing bugs.++## Key Features++- **Composable**: Build statements using `Semigroup`/`Monoid` operations+- **Type-safe**: Automatic parameter encoding with type-driven encoder derivation+- **Dynamic**: Construct SQL conditionally based on parameters+- **Clean API**: No manual placeholder numbering or encoder alignment++## Quick Example++```haskell+import Hasql.DynamicStatements.Snippet++-- Build a dynamic substring query+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 <>+  ")"++-- Execute in a session+result <- toSession (selectSubstring "hello" (Just 2) Nothing) +  (singleRow $ column $ nonNullable text)+```++## Without Snippet API++Compare the above to manual construction:++```haskell+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.param (string >$ Encoders.text) <>+        foldMap (\x -> Encoders.param (x >$ Encoders.int8)) from <>+        foldMap (\x -> Encoders.param (x >$ Encoders.int8)) to+      decoder = singleRow $ column $ nonNullable text+  in Statement.preparable sql encoder decoder+```++The Snippet API eliminates placeholder numbering, pattern matching on parameter presence, and manual encoder sequencing.++## Core API++### Construction++- **`sql`** - Raw SQL text+- **`param`** - Parameter with implicit encoder+- **`encoderAndParam`** - Parameter with explicit encoder++### Execution++- **`toSql`** - Compile to SQL text with placeholders+- **`toStatement`** - Create a `Statement`+- **`toSession`** - Execute directly in `Session`+- **`toPipeline`** - Execute in `Pipeline`
hasql-dynamic-statements.cabal view
@@ -1,10 +1,31 @@ cabal-version: 3.0 name: hasql-dynamic-statements-version: 0.5+version: 0.5.0.1 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.+  Library extending Hasql with a composable API for building SQL statements dynamically. +  The core abstraction is 'Snippet', which allows you to construct SQL statements with parameters+  injected in-place, automatically handling placeholders and encoder matching. This is particularly+  useful when the structure of your SQL depends on runtime parameters.++  Key features:++  * Composable SQL snippets using 'Semigroup' and 'Monoid' instances+  * Automatic placeholder generation and parameter encoding+  * Type-safe parameter injection with implicit encoder derivation+  * Protection against common SQL construction bugs+  * Support for conditional SQL structure based on parameters++  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 <>+  >   ")"+ homepage: https://github.com/nikita-volkov/hasql-dynamic-statements bug-reports:   https://github.com/nikita-volkov/hasql-dynamic-statements/issues@@ -16,6 +37,7 @@ license-file: LICENSE extra-doc-files:   CHANGELOG.md+  README.md  source-repository head   type: git
src/library/Hasql/DynamicStatements/Snippet.hs view
@@ -45,23 +45,29 @@ -- '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 ())+data Snippet+  = Snippet+      (Int -> TextBuilder.TextBuilder)+      Int+      (Encoders.Params ()) -deriving instance Semigroup Snippet+instance Semigroup Snippet where+  Snippet sql1 paramCount1 encoder1 <> Snippet sql2 paramCount2 encoder2 =+    Snippet+      (\paramId -> sql1 paramId <> sql2 (paramId + paramCount1))+      (paramCount1 + paramCount2)+      (encoder1 <> encoder2) -deriving instance Monoid Snippet+instance Monoid Snippet where+  mempty = Snippet (\_ -> mempty) 0 mempty  instance IsString Snippet where-  fromString x = Snippet (pure (StringSnippetChunk (fromString x)))+  fromString = sql . fromString  -- | -- SQL chunk. sql :: Text -> Snippet-sql x = Snippet (pure (StringSnippetChunk (TextBuilder.text x)))+sql x = Snippet (const (TextBuilder.text x)) 0 mempty  -- | -- Parameter encoded using an implicitly derived encoder from the type.@@ -71,26 +77,17 @@ -- | -- 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)+encoderAndParam encoder param =+  Snippet+    (\paramId -> "$" <> TextBuilder.decimal paramId)+    1+    (param >$ Encoders.param encoder)  -- | -- Compile a snippet into SQL text with placeholders. toSql :: Snippet -> Text-toSql snippet = fst (compile snippet)+toSql (Snippet sql _ _) =+  TextBuilder.toText (sql 1)  -- | -- Construct a statement dynamically, specifying the parameters in-place@@ -136,9 +133,8 @@ -- 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+toStatement (Snippet sql _ encoder) =+  Statement.unpreparable (TextBuilder.toText (sql 1)) encoder  -- | -- Execute in @Session.Session@, providing a result decoder.