packages feed

sqlc-hs 0.2.0.1 → 0.3.0.0

raw patch · 33 files changed

+3060/−70 lines, 33 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Sqlc.Hs.Resolve: newResolveType :: Config -> Text -> ResolveType
+ Sqlc.Hs.Backend: Hasql :: Backend
+ Sqlc.Hs.Backend: Mysql :: Backend
+ Sqlc.Hs.Backend: PostgresqlSimple :: Backend
+ Sqlc.Hs.Backend: Sqlite :: Backend
+ Sqlc.Hs.Backend: data Backend
+ Sqlc.Hs.Backend: instance GHC.Classes.Eq Sqlc.Hs.Backend.Backend
+ Sqlc.Hs.Backend: instance GHC.Show.Show Sqlc.Hs.Backend.Backend
+ Sqlc.Hs.Backend: resolveBackend :: Text -> Maybe Text -> Either Text (Maybe Backend)
+ Sqlc.Hs.Config: [driver] :: Config -> Maybe Text
+ Sqlc.Hs.Config: [hasqlDecoder] :: Override -> Maybe Text
+ Sqlc.Hs.Config: [hasqlEncoder] :: Override -> Maybe Text
+ Sqlc.Hs.Resolve: findOverride :: Config -> Text -> Column -> Maybe Override
+ Sqlc.Hs.Resolve: hasqlColumnCodec :: Maybe Override -> Column -> (Text, Text)
+ Sqlc.Hs.Resolve: newBuiltinResolver :: Maybe Backend -> Text -> ResolveType
+ Sqlc.Hs.Resolve: newOverrideResolver :: Config -> Text -> ResolveType
+ Sqlc.Hs.Resolve: rewriteSlices :: [Int] -> Text -> Either Text Text
- Sqlc.Hs.Config: Config :: Maybe Text -> Maybe Text -> [Text] -> Maybe Text -> [Vector Override] -> Naming -> Config
+ Sqlc.Hs.Config: Config :: Maybe Text -> Maybe Text -> [Text] -> Maybe Text -> [Vector Override] -> Naming -> Maybe Text -> Config
- Sqlc.Hs.Config: Override :: NonEmpty HaskellType -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Bool -> Override
+ Sqlc.Hs.Config: Override :: NonEmpty HaskellType -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Bool -> Maybe Text -> Maybe Text -> Override

Files

CHANGELOG.md view
@@ -1,5 +1,27 @@ # Revision history for sqlc-haskell +## 0.3.0.0 -- 2026-08-20++* A hasql backend for PostgreSQL, selected with the new `driver` option+  (`driver: hasql`). The default stays `postgresql-simple`, so existing+  configurations generate the same code as before. Requires hasql >= 1.10.+  The generated internal module declares the `ToRow`/`FromRow` and+  `ToField`/`FromField` classes hasql does not ship, and overrides can name+  their codecs with the new `hasql_encoder` and `hasql_decoder` keys.+* Normalise numbered `?N` placeholders (emitted by sqlc for `sqlc.arg`)+  to positional `?` so sqlite-simple can parse the query (SQLite).+* The hasql runners' error type is named through a `RunnerError` alias+  that CPP picks per hasql version, so the generated module compiles+  against both hasql 2.0 (`SessionError`) and hasql 2.1, which replaced+  it with `UseError` (hasql).+* The generated hasql `ToRow`/`FromRow` instances carry `INLINE`. A row+  decoder is a chain of `<$>` and `<*>`, and without an unfolding at the+  instance that chain cannot collapse at its definition site, so every+  column of every row pays for the closures it is made of. Pairs with+  nikita-volkov/hasql#340, which fixes the same problem inside hasql;+  together they cut allocation for a 1001-row two-column decode by+  10.2% (hasql).+ ## 0.2.0.1 -- 2026-07-14  * Mustache-style naming templates for generated declarations.
README.md view
@@ -2,7 +2,7 @@  A Haskell code generator plugin for [sqlc](https://github.com/kyleconroy/sqlc), allowing you to generate idiomatic Haskell types and functions directly from your SQL queries. -It leverages [postgresql-simple](https://hackage.haskell.org/package/postgresql-simple), [mysql-simple](https://hackage.haskell.org/package/mysql-simple), and [sqlite-simple](https://hackage.haskell.org/package/sqlite-simple), generating a thin layer on top of these well-known libraries.+It leverages [postgresql-simple](https://hackage.haskell.org/package/postgresql-simple), [hasql](https://hackage.haskell.org/package/hasql), [mysql-simple](https://hackage.haskell.org/package/mysql-simple), and [sqlite-simple](https://hackage.haskell.org/package/sqlite-simple), generating a thin layer on top of these well-known libraries.  ## Installation @@ -67,6 +67,136 @@                 type: Data.ByteString.ByteString ``` +## Drivers++`driver` selects the Haskell library the generated code is written against.+Every engine has a default, so configurations that don't set it keep generating+exactly the code they did before.++| Engine       | `driver`                     | Default             |+| ------------ | ---------------------------- | ------------------- |+| `postgresql` | `postgresql-simple`, `hasql` | `postgresql-simple` |+| `mysql`      | `mysql-simple`               | `mysql-simple`      |+| `sqlite`     | `sqlite-simple`              | `sqlite-simple`     |++```yaml+sql:+  - engine: postgresql+    queries: query.sql+    schema: schema.sql+    codegen:+      - out: gen+        plugin: haskell+        options:+          driver: hasql+          cabal_package_name: your-package+```++### The hasql driver++Requires **hasql >= 1.10**, 2.x included. The generated cabal file depends on+`hasql` without a version bound, like every other dependency it emits, so an+older `hasql` pinned elsewhere in your project shows up as a compile error+rather than a solver one.++The generated `Queries.Internal` module gives every query the same helpers as+the other drivers do — `exec`, `execRows`, `execResult`, `queryOne`,+`queryMany`, `fold` and `execMany` — taking a `Hasql.Connection.Connection` and+returning `IO (Either Hasql.Errors.SessionError a)`:++```haskell+users <- queryMany connection query_ListUsers Params_ListUsers {age = 42}+```++Each of those runs its query in a session of its own. Every one also comes as a+`…Session` variant returning a `Hasql.Session.Session`, for when several+queries have to share one session:++```haskell+result <-+  Hasql.Connection.use connection $ do+    _ <- execSession query_InsertAuthor (Params_InsertAuthor {name = "Kafka"})+    queryManySession query_ListAuthors Params_ListAuthors {}+```++Two things work differently from the postgresql-simple driver:++* `fold`'s step function is pure (`a -> Result name -> a`) — hasql folds a+  result without `IO`.+* `sqlc.slice` parameters are bound as one array parameter, which PostgreSQL+  only accepts with the array operators, so `IN ($1)` is generated as+  `= ANY ($1)` and `NOT IN ($1)` as `<> ALL ($1)`. A slice used anywhere else+  is an error; write `= ANY(sqlc.arg(...)::type[])` in your SQL instead of+  using `sqlc.slice`.++#### Codecs++hasql has no `ToField`-style class. Its encoders and decoders are values, chosen+per SQL type, and from 1.10 on it checks that a column's type matches the+decoder reading it — so `text`, `varchar` and `bpchar`, all `Text` on the+Haskell side, each need their own decoder. sqlc-hs picks the codec from the SQL+type sqlc reported, for every type it knows.++Columns typed by an `overrides` entry are a different matter: sqlc-hs cannot+know what codec your type wants. Those go through the `ToField` and `FromField`+classes that the generated `Queries.Internal` module declares:++```haskell+class ToField a   where toField   :: Hasql.Encoders.Value a+class FromField a where fromField :: Hasql.Decoders.Value a+```++Instances ship for `Bool`, the sized `Int`s, `Float`, `Double`, `Scientific`,+`Char`, `Text`, `ByteString`, `UUID`, `Day`, `LocalTime`, `UTCTime`,+`TimeOfDay`, `(TimeOfDay, TimeZone)`, `DiffTime`, `Data.Aeson.Value`, and lists+and vectors of those. The usual overrides therefore need nothing extra — a+`uuid` column mapped to `Data.UUID.UUID`, a `timestamptz` to `UTCTime`, a `date`+to `Day` all work as they stand. For a type of your own, write the instance:++```haskell+instance ToField UserId where+  toField = Data.Functor.Contravariant.contramap unUserId toField++instance FromField UserId where+  fromField = fmap UserId fromField+```++When an instance is the wrong place for it, because the same Haskell type wants+different codecs in different columns, an override can name the codecs itself+with `hasql_encoder` and `hasql_decoder`. `UTCTime`'s instance is+`timestamptz`, so a `timestamp` column read as a `UTCTime` has to spell out the+conversion:++```yaml+overrides:+  - db_type: pg_catalog.timestamp+    haskell_type:+      package: time+      module: Data.Time+      type: Data.Time.UTCTime+    hasql_encoder: Data.Functor.Contravariant.contramap (Data.Time.utcToLocalTime Data.Time.utc) Hasql.Encoders.timestamp+    hasql_decoder: fmap (Data.Time.localTimeToUTC Data.Time.utc) Hasql.Decoders.timestamp+```++Both are Haskell expressions spliced into the generated code, and both have to+be given together.++Enum codecs are generated into `Queries.Types` from the catalog, so enums need+nothing either.++#### Limitations++* `money` and `name` columns cannot be typed: hasql ships no codec for either,+  so sqlc-hs reports them as unresolved rather than generating a decoder that+  fails against the server. To use one anyway, give it an override whose+  `hasql_encoder`/`hasql_decoder` are built with `Hasql.Encoders.custom` and+  `Hasql.Decoders.custom`.+* A value cannot be decoded from a SQL type it is not stored as.+  postgresql-simple parses the text form, so `CAST(created_at AS TEXT)` can be+  read as a `UTCTime` (see the override examples below); hasql decodes the+  binary form and checks the column's type, so the SQL type and the codec have+  to agree.+ ## Overrides  `overrides` is a **list** of mappings — each entry tells sqlc-hs to map a@@ -84,6 +214,8 @@ | `column`       | no*      | Match a specific column: `column`, `table.column` or `schema.table.column`. The table part matches the table name or its query alias; a bare `column` also matches aliased expression outputs (e.g. `CAST(... AS TEXT) AS created_at`), which carry no table. | | `engine`       | no       | Restrict the override to a specific engine (`postgresql`, `mysql`, or `sqlite`). Useful when one configuration targets multiple engines.                      | | `nullable`     | no       | If `true`, only match columns that are nullable. If `false` or omitted, only match columns that are `NOT NULL`.                                              |+| `hasql_encoder` | no      | hasql only: the `Hasql.Encoders.Value` expression to encode matching columns with. Must be given together with `hasql_decoder`. See [The hasql driver](#the-hasql-driver). |+| `hasql_decoder` | no      | hasql only: the `Hasql.Decoders.Value` expression to decode matching columns with. Must be given together with `hasql_encoder`.                              |  \* At least one of `db_type` or `column` must be given. When both are given, both must match.
sqlc-hs.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.4 name:               sqlc-hs-version:            0.2.0.1+version:            0.3.0.0  -- synopsis: -- description:@@ -16,13 +16,14 @@  description:   A Haskell code generator plugin for sqlc, allowing you to generate idiomatic Haskell types and functions directly from your SQL queries.-  It leverages postgresql-simple, mysql-simple, and sqlite-simple, generating a thin layer on top of these well-known libraries.+  It leverages postgresql-simple, hasql, mysql-simple, and sqlite-simple, generating a thin layer on top of these well-known libraries.  -- copyright: build-type:         Simple extra-doc-files:    CHANGELOG.md extra-source-files:   README.md+  templates/internal.hasql.hs.jinja   templates/internal.mysql.hs.jinja   templates/internal.postgresql.hs.jinja   templates/internal.sqlite.hs.jinja@@ -78,6 +79,7 @@ library   import:           stuff   exposed-modules:+    Sqlc.Hs.Backend     Sqlc.Hs.Codegen     Sqlc.Hs.Config     Sqlc.Hs.Main
+ src/Sqlc/Hs/Backend.hs view
@@ -0,0 +1,70 @@+-- | The database library the generated code is written against.+--+-- sqlc tells us the /engine/ (@postgresql@, @mysql@, @sqlite@); the @driver@+-- configuration option picks between the libraries available for it. Only+-- PostgreSQL currently has more than one.+module Sqlc.Hs.Backend+  ( Backend (..),+    resolveBackend,+  )+where++import Data.List (lookup)+import Data.Text qualified++data Backend+  = -- | <https://hackage.haskell.org/package/postgresql-simple postgresql-simple>+    PostgresqlSimple+  | -- | <https://hackage.haskell.org/package/hasql hasql>+    Hasql+  | -- | <https://hackage.haskell.org/package/sqlite-simple sqlite-simple>+    Sqlite+  | -- | <https://hackage.haskell.org/package/mysql-simple mysql-simple>+    Mysql+  deriving stock (Eq, Show)++-- | Pick the backend for an engine and a configured driver.+--+-- The engine comes from the 'GenerateRequest' settings. sqlc always reports one;+-- 'Nothing' stands for a request that didn't, where there is nothing to pick+-- from and we keep to what sqlc-hs has always generated for it: the+-- postgresql-simple internal module, and no per-query instances.+resolveBackend ::+  -- | Engine, e.g. @postgresql@. May be empty.+  Text ->+  -- | The @driver@ option, if configured.+  Maybe Text ->+  Either Text (Maybe Backend)+resolveBackend engine driver =+  case driver of+    Nothing+      | engine == mempty ->+          Right Nothing+      | otherwise ->+          Right (Just defaultBackend)+    Just driver+      | Just backend <- lookup driver drivers ->+          Right (Just backend)+      | otherwise ->+          Left $+            "Unknown driver "+              <> show driver+              <> " for engine "+              <> show engine+              <> ". Valid drivers are: "+              <> Data.Text.intercalate ", " (map fst drivers)+              <> "."+  where+    -- The drivers available for this engine, the default one first.+    (defaultBackend, drivers) =+      case engine of+        "sqlite" ->+          (Sqlite, [("sqlite-simple", Sqlite)])+        "mysql" ->+          (Mysql, [("mysql-simple", Mysql)])+        _ ->+          ( PostgresqlSimple,+            [ ("postgresql-simple", PostgresqlSimple),+              ("hasql", Hasql)+            ]+          )
src/Sqlc/Hs/Codegen.hs view
@@ -10,7 +10,8 @@ import Data.List (lookup) import Data.ProtoLens.Labels () import Proto.Protos.Codegen qualified-import Sqlc.Hs.Config (Config (..), HaskellType (..))+import Sqlc.Hs.Backend (Backend (..), resolveBackend)+import Sqlc.Hs.Config (Config (..), HaskellType (..), Override (..)) import Sqlc.Hs.Resolve   ( ResolveName,     ResolveType,@@ -18,12 +19,16 @@     determineInternalModule,     determineTopLevelModule,     determineTypesModule,+    findOverride,+    hasqlColumnCodec,     mangleQuery,+    newBuiltinResolver,     newEnumResolver,-    newResolveType,+    newOverrideResolver,     queryParamBindings,     resolveQueryName,     resolveType,+    rewriteSlices,   ) import System.Exit qualified import System.IO qualified@@ -50,16 +55,26 @@  codegen :: Config -> Proto.Protos.Codegen.GenerateRequest -> IO [File] codegen config generateRequest = do+  backend <-+    case resolveBackend engine config.driver of+      Left errorMessage -> do+        System.IO.hPutStrLn System.IO.stderr (toString errorMessage)+        System.Exit.exitWith (System.Exit.ExitFailure 1)+      Right backend ->+        pure backend+   typesModule <-     codegenTypes-      (generateRequest ^. #settings . #engine)+      backend       internalName       typesName       resolveName+      (generateRequest ^. #catalog . #defaultSchema)       (generateRequest ^. #catalog . #schemas)    let resolveType =-        newResolveType config (generateRequest ^. #settings . #engine)+        newOverrideResolver config engine+          <> newBuiltinResolver backend engine           <> newEnumResolver             ( HaskellType                 { module' = Just typesModule.name,@@ -75,7 +90,9 @@   modules <-     traverse       ( codegenQuery-          (generateRequest ^. #settings . #engine)+          backend+          engine+          (findOverride config engine)           internalName           resolveName           resolveType@@ -86,7 +103,7 @@     codegenToplevel toplevelName internalName typesName modules    internalModule <--    codegenInternal (generateRequest ^. #settings . #engine) internalName+    codegenInternal backend internalName    let generatedModules =         toplevelModule : typesModule : internalModule : modules@@ -96,6 +113,9 @@    pure (cabalPackageFile <> map moduleToFile generatedModules)   where+    engine =+      generateRequest ^. #settings . #engine+     resolveName =       resolveQueryName config.naming config.haskellModulePrefix @@ -149,11 +169,11 @@           error (show errorDoc)  codegenInternal ::-  Text ->+  Maybe Backend ->   -- | ResolvedName of the internal module name   ResolvedNames ->   IO Module-codegenInternal engine internal = do+codegenInternal backend internal = do   let context =         Text.EDE.fromPairs           [ "moduleName" Text.EDE..= internal.toHaskellModuleName@@ -173,20 +193,35 @@       }   where     (template, dependencies) =-      case engine of-        "sqlite" ->+      case backend of+        Just Sqlite ->           ( internalSqliteTemplate,             [ HaskellType {package = Just "sqlite-simple", module' = Just "Database.SQLite.Simple", name = Just "ToRow"},               HaskellType {package = Just "sqlite-simple", module' = Just "Database.SQLite.Simple", name = Just "FromRow"},               HaskellType {package = Just "vector", module' = Just "Data.Vector", name = Just "Vector"}             ]           )-        "mysql" ->+        Just Mysql ->           ( internalMysqlTemplate,             [ HaskellType {package = Just "mysql-simple", module' = Just "Database.MySQL.Simple", name = Just "ToRow"},               HaskellType {package = Just "mysql-simple", module' = Just "Database.MySQL.Simple", name = Just "FromRow"}             ]           )+        Just Hasql ->+          ( internalHasqlTemplate,+            -- The ToField/FromField instances the internal module ships cover+            -- the types the built-in mappings and the common overrides use.+            -- Every one of these packages is already in hasql's own dependency+            -- closure, so none of them costs an extra build.+            [ HaskellType {package = Just "hasql", module' = Just "Hasql.Session", name = Just "Session"},+              HaskellType {package = Just "aeson", module' = Just "Data.Aeson", name = Just "Value"},+              HaskellType {package = Just "scientific", module' = Just "Data.Scientific", name = Just "Scientific"},+              HaskellType {package = Just "time", module' = Just "Data.Time", name = Just "UTCTime"},+              HaskellType {package = Just "uuid", module' = Just "Data.UUID", name = Just "UUID"},+              HaskellType {package = Just "vector", module' = Just "Data.Vector", name = Just "Vector"}+            ]+          )+        -- Also the fallback for a request that reported no engine at all.         _ ->           ( internalPostgresTemplate,             [ HaskellType {package = Just "postgresql-simple", module' = Just "Database.PostgreSQL.Simple", name = Just "ToRow"},@@ -253,26 +288,29 @@       sort (ordNub config.cabalDefaultExtensions)  codegenTypes ::-  -- | Engine, if defined-  Text ->+  Maybe Backend ->   -- | ResolvedName of the internal module   ResolvedNames ->   -- | ResolvedName of the types module   ResolvedNames ->   ResolveName ->+  -- | The catalog's default schema, if reported+  Text ->   [Proto.Protos.Codegen.Schema] ->   IO Module-codegenTypes engine internalModule typesModule resolveName schemas = do+codegenTypes backend internalModule typesModule resolveName defaultSchema schemas = do   let context =         Text.EDE.fromPairs-          [ "generatePostgresql" Text.EDE..= (engine == "postgresql"),-            "generateSqlite" Text.EDE..= (engine == "sqlite"),-            "generateMysql" Text.EDE..= (engine == "mysql"),+          [ "generatePostgresql" Text.EDE..= (backend == Just PostgresqlSimple),+            "generateHasql" Text.EDE..= (backend == Just Hasql),+            "generateSqlite" Text.EDE..= (backend == Just Sqlite),+            "generateMysql" Text.EDE..= (backend == Just Mysql),             "moduleName" Text.EDE..= typesModule.toHaskellModuleName,             "internalModuleName" Text.EDE..= internalModule.toHaskellModuleName,             "enums"               Text.EDE..= [ Text.EDE.fromPairs                               [ "escapedEnumName" Text.EDE..= show @Text (enum ^. #name),+                                "escapedEnumSchema" Text.EDE..= enumSchema (schema ^. #name),                                 "values"                                   Text.EDE..= [ Text.EDE.fromPairs                                                   [ "escapedEnumValue" Text.EDE..= show @Text value,@@ -297,6 +335,20 @@         contents = contents context       }   where+    -- hasql resolves an enum's OID by name at runtime. An unqualified name is+    -- looked up through the search path, which is what we want for the default+    -- schema; anything else has to be qualified.+    enumSchema :: Text -> Text+    enumSchema schema+      | schema == mempty || schema == defaultSchemaName =+          "Prelude.Nothing"+      | otherwise =+          "(Prelude.Just " <> show @Text schema <> ")"++    defaultSchemaName+      | defaultSchema == mempty = "public"+      | otherwise = defaultSchema+     contents context =       case Text.EDE.render typesTemplate context of         Text.EDE.Success output ->@@ -307,15 +359,18 @@ -- | Generate a file for a single query. This returns the resolved 'HaskellType's so -- that we can generate the necessary build-depends for the cabal file. codegenQuery ::+  Maybe Backend ->   -- | Engine, if defined   Text ->+  -- | The override that matched a column, if any. Determines the hasql codec.+  (Proto.Protos.Codegen.Column -> Maybe Override) ->   -- | ResolvedName of the internal module name   ResolvedNames ->   ResolveName ->   ResolveType ->   Proto.Protos.Codegen.Query ->   IO Module-codegenQuery engine internalModule resolveName resolver query = do+codegenQuery backend engine resolveOverride internalModule resolveName resolver query = do   let resolvedName =         resolveName (query ^. #name) @@ -331,12 +386,37 @@       whenNothing (resolveType resolver column) $         couldNotResolveType column +  sql <-+    case backend of+      -- hasql speaks PostgreSQL's own numbered placeholders, so the SQL is+      -- passed through as sqlc emitted it. Slices are the exception: they+      -- become a single array parameter and need the array operators.+      Just Hasql ->+        case rewriteSlices (sliceNumbers parameterColumns) (query ^. #text) of+          Left errorMessage -> do+            System.IO.hPutStrLn System.IO.stderr $+              "In query "+                <> show (query ^. #name)+                <> ": "+                <> toString errorMessage+            System.Exit.exitWith (System.Exit.ExitFailure 1)+          Right sql ->+            pure sql+      _ ->+        pure (mangleQuery (query ^. #text))+   let importedTypes :: [HaskellType]       importedTypes =         foldMap (toList . snd . snd) parameterColumns           <> foldMap (toList . snd) resultColumns           <> [ HaskellType {package = Just "base", module' = Just "Data.Foldable", name = Nothing}              ]+          <> case backend of+            -- The parameter encoders are assembled contravariantly.+            Just Hasql ->+              [HaskellType {package = Just "base", module' = Just "Data.Functor.Contravariant", name = Nothing}]+            _ ->+              []        -- Modules that the query module needs to import.       imports :: [Text]@@ -346,9 +426,10 @@        context =         Text.EDE.fromPairs-          [ "generatePostgresql" Text.EDE..= (engine == "postgresql"),-            "generateSqlite" Text.EDE..= (engine == "sqlite"),-            "generateMysql" Text.EDE..= (engine == "mysql"),+          [ "generatePostgresql" Text.EDE..= (backend == Just PostgresqlSimple),+            "generateHasql" Text.EDE..= (backend == Just Hasql),+            "generateSqlite" Text.EDE..= (backend == Just Sqlite),+            "generateMysql" Text.EDE..= (backend == Just Mysql),             "sourceFile" Text.EDE..= (query ^. #filename),             "moduleName" Text.EDE..= resolvedName.toHaskellModuleName,             "moduleImports" Text.EDE..= imports,@@ -359,9 +440,10 @@             "haskellResultName" Text.EDE..= resolvedName.toResultConstructorDeclarationName,             "escapedQueryName" Text.EDE..= show @Text (query ^. #name),             "escapedCommand" Text.EDE..= show @Text (query ^. #cmd),-            "escapedSql" Text.EDE..= show @Text mangledQuery,+            "escapedSql" Text.EDE..= show @Text sql,             "parameterColumns" Text.EDE..= fmap (toParameterColumn . snd) parameterColumns,             "queryColumns" Text.EDE..= fmap toParameterColumn (toQueryColumns parameterColumns),+            "encoderColumns" Text.EDE..= fmap (toParameterColumn . snd) (sortOn fst parameterColumns),             "resultColumns" Text.EDE..= fmap toResultColumn resultColumns           ] @@ -373,8 +455,11 @@         contents = contents context       }   where-    mangledQuery =-      mangleQuery (query ^. #text)+    sliceNumbers parameterColumns =+      [ fromIntegral number+        | (number, (column, _haskellTypes)) <- parameterColumns,+          column ^. #isSqlcSlice+      ]      -- It's possible for parametes to appear in a query more than once.     -- This function "zips" the occurrences in the query with the actual@@ -399,6 +484,7 @@         [ "name" Text.EDE..= (resolveName (column ^. #name)).toFieldName column,           "type" Text.EDE..= encodeColumnType haskellType,           "notNull" Text.EDE..= (column ^. #notNull),+          "encoder" Text.EDE..= fst (hasqlCodec column),           "slice"             Text.EDE..= if column ^. #isSqlcSlice               then Just (show @Text ("/*SLICE:" <> column ^. #name <> "*/?"))@@ -408,9 +494,16 @@     toResultColumn (column, haskellType :| _) =       Text.EDE.fromPairs         [ "name" Text.EDE..= (resolveName (column ^. #name)).toFieldName column,-          "type" Text.EDE..= encodeColumnType haskellType+          "type" Text.EDE..= encodeColumnType haskellType,+          "decoder" Text.EDE..= snd (hasqlCodec column)         ] +    -- Only the hasql templates read the "encoder" and "decoder" fields.+    hasqlCodec column =+      case backend of+        Just Hasql -> hasqlColumnCodec (resolveOverride column) column+        _ -> (mempty, mempty)+     encodeColumnType haskellType =       haskellType.name @@ -445,6 +538,10 @@ internalSqliteTemplate :: Text.EDE.Template internalSqliteTemplate =   toTemplate $(Data.FileEmbed.embedFile "templates/internal.sqlite.hs.jinja")++internalHasqlTemplate :: Text.EDE.Template+internalHasqlTemplate =+  toTemplate $(Data.FileEmbed.embedFile "templates/internal.hasql.hs.jinja")  cabalTemplate :: Text.EDE.Template cabalTemplate =
src/Sqlc/Hs/Config.hs view
@@ -33,7 +33,10 @@     -- concatenation. No need to create a one big vector.     overrides :: [Vector Override],     -- | Templates for the names of generated declarations.-    naming :: Naming+    naming :: Naming,+    -- | The database library to generate against, e.g. @hasql@. Which drivers+    -- are available depends on the engine; see 'Sqlc.Hs.Backend.resolveBackend'.+    driver :: Maybe Text   }  instance Semigroup Config where@@ -50,7 +53,9 @@         overrides =           config1.overrides <> config2.overrides,         naming =-          config1.naming <> config2.naming+          config1.naming <> config2.naming,+        driver =+          getFirst $ First config1.driver <> First config2.driver       }  instance Monoid Config where@@ -61,7 +66,8 @@         cabalPackageName = Nothing,         cabalPackageVersion = Nothing,         cabalDefaultExtensions = [],-        naming = mempty+        naming = mempty,+        driver = Nothing       }  instance FromJSON Config where@@ -73,6 +79,7 @@       <*> o .:? "haskell_module_prefix"       <*> fmap pure (o .:? "overrides" .!= mempty)       <*> o .:? "naming" .!= mempty+      <*> o .:? "driver"  -- | Mustache-style templates for the names of generated declarations. Every -- field is optional; an omitted template falls back to the default that@@ -143,7 +150,15 @@     -- | For global overrides only when two different engines are in use.     engine :: Maybe Text,     -- | True if the haskellType should override if the matching type is nullable-    nullable :: Maybe Bool+    nullable :: Maybe Bool,+    -- | Encoder expression to use for matching columns, e.g.+    -- @Hasql.Encoders.timestamp@. hasql only; must be given together with+    -- 'hasqlDecoder'. Without them a matched column goes through the generated+    -- @ToField@/@FromField@ classes.+    hasqlEncoder :: Maybe Text,+    -- | Decoder expression to use for matching columns, e.g.+    -- @Hasql.Decoders.timestamp@. See 'hasqlEncoder'.+    hasqlDecoder :: Maybe Text   }  instance FromJSON Override where@@ -160,8 +175,12 @@         <*> o .:? "column"         <*> o .:? "engine"         <*> o .:? "nullable"+        <*> o .:? "hasql_encoder"+        <*> o .:? "hasql_decoder"     when (isNothing override.databaseType && isNothing override.column) $       fail "override requires at least one of \"db_type\" or \"column\""+    when (isJust override.hasqlEncoder /= isJust override.hasqlDecoder) $+      fail "override requires either both or neither of \"hasql_encoder\" and \"hasql_decoder\""     pure override  -- | A haskell type denotes a fully qualified data type with module@@ -205,5 +224,6 @@       cabalPackageVersion = Just "0.1.0.0",       cabalDefaultExtensions = [],       haskellModulePrefix = Just "Queries",-      overrides = []+      overrides = [],+      driver = Nothing     }
src/Sqlc/Hs/Resolve.hs view
@@ -1,8 +1,10 @@ module Sqlc.Hs.Resolve   ( ResolveType,     resolveType,-    newResolveType,+    newOverrideResolver,+    newBuiltinResolver,     newEnumResolver,+    findOverride,     -- | How to resolve names to Haskell modules and files     ResolveName,     ResolvedNames (..),@@ -14,6 +16,9 @@     -- | Query mangling     mangleQuery,     queryParamBindings,+    rewriteSlices,+    -- | hasql codecs+    hasqlColumnCodec,   ) where @@ -23,6 +28,7 @@ import Data.Text qualified import Data.Vector (Vector) import Proto.Protos.Codegen qualified+import Sqlc.Hs.Backend (Backend (..)) import Sqlc.Hs.Config (Config (..), HaskellType (..), Naming (..), Override (..), defaultConfig) import Sqlc.Hs.NameTemplate qualified import System.FilePath ((<.>))@@ -257,12 +263,44 @@ resolveType :: ResolveType -> Proto.Protos.Codegen.Column -> Maybe (Proto.Protos.Codegen.Column, NonEmpty HaskellType) resolveType = coerce -newResolveType ::+-- | The user's @overrides@, in configuration order.+newOverrideResolver ::   Config ->   -- | Engine, if defined   Text ->   ResolveType-newResolveType config engine = ResolveType $ \column ->+newOverrideResolver config engine =+  fromMatchers engine (map overrideToMatcher (configOverrides config))++-- | The type mappings sqlc-hs knows out of the box.+newBuiltinResolver ::+  Maybe Backend ->+  -- | Engine, if defined+  Text ->+  ResolveType+newBuiltinResolver backend engine =+  fromMatchers engine (builtins backend)++-- | The first override matching a column, if any. 'newOverrideResolver' tells+-- you /that/ an override matched; this tells you /which/ one, which is what+-- carries the hasql codecs.+findOverride ::+  Config ->+  -- | Engine, if defined+  Text ->+  Proto.Protos.Codegen.Column ->+  Maybe Override+findOverride config engine column =+  find+    (\override -> matchesEngine engine override.engine && overrideMatches override column)+    (configOverrides config)++configOverrides :: Config -> [Override]+configOverrides config =+  toList (Overrides config.overrides)++fromMatchers :: Text -> [Matcher] -> ResolveType+fromMatchers engine allMatchers = ResolveType $ \column ->   case mapMaybe (\matcher -> matcher.matcher column) matchers of     haskellTypes : _ ->       Just (column, haskellTypes)@@ -271,20 +309,22 @@   where     matchers :: [Matcher]     matchers =-      [ matcher-        | matcher <--            concat-              [ map overrideToMatcher (toList (Overrides config.overrides)),-                builtins-              ],-          -- In case the GenerateRequest didn't specify an engine.-          engine == mempty-            -- In case the matcher is engine generic-            || isNothing matcher.engine-            -- In case matcher engine and requested engine match-            || matcher.engine == Just engine-      ]+      filter (matchesEngine engine . (.engine)) allMatchers +matchesEngine ::+  -- | The requested engine, if defined+  Text ->+  -- | The engine a matcher is restricted to, if any+  Maybe Text ->+  Bool+matchesEngine engine matcherEngine =+  -- In case the GenerateRequest didn't specify an engine.+  engine == mempty+    -- In case the matcher is engine generic+    || isNothing matcherEngine+    -- In case matcher engine and requested engine match+    || matcherEngine == Just engine+ newEnumResolver ::   HaskellType ->   [Proto.Protos.Codegen.Enum] ->@@ -321,24 +361,29 @@         haskellType {name = fmap wrapParenthesis haskellType.name}           :| haskellTypes -    -- Every constraint present on the override must hold: db_type (if given)-    -- and column (if given). The FromJSON instance guarantees at least one of-    -- the two is set, so this can never match unconditionally.     matchType column-      | fromMaybe False override.nullable /= not (column ^. #notNull) =-          Nothing-      | matchesDatabaseType column,-        matchesColumn column =+      | overrideMatches override column =           Just override.haskellType       | otherwise =           Nothing -    matchesDatabaseType column =+-- | Every constraint present on the override must hold: db_type (if given) and+-- column (if given). The FromJSON instance guarantees at least one of the two+-- is set, so this can never match unconditionally.+overrideMatches :: Override -> Proto.Protos.Codegen.Column -> Bool+overrideMatches override column =+  and+    [ fromMaybe False override.nullable == not (column ^. #notNull),+      matchesDatabaseType,+      matchesColumn+    ]+  where+    matchesDatabaseType =       case override.databaseType of         Nothing -> True         Just databaseType -> columnDataType (column ^. #type') == databaseType -    matchesColumn column =+    matchesColumn =       case override.column of         Nothing -> True         Just name -> columnMatches name column@@ -391,9 +436,9 @@                 Nothing     } -builtins :: [Matcher]-builtins =-  [ Matcher {engine = Just "postgresql", matcher = postgresBuiltin},+builtins :: Maybe Backend -> [Matcher]+builtins backend =+  [ Matcher {engine = Just "postgresql", matcher = postgresBuiltin backend},     Matcher {engine = Just "mysql", matcher = mysqlBuiltin},     Matcher {engine = Just "sqlite", matcher = sqliteBuiltin}   ]@@ -687,8 +732,8 @@       | otherwise =           Nothing -postgresBuiltin :: Proto.Protos.Codegen.Column -> Maybe (NonEmpty HaskellType)-postgresBuiltin column =+postgresBuiltin :: Maybe Backend -> Proto.Protos.Codegen.Column -> Maybe (NonEmpty HaskellType)+postgresBuiltin backend column =   applyNullable column $     applyArrayLike column identity $       asum@@ -700,14 +745,25 @@           pgType ["smallint", "int2", "pg_catalog.int2"] "base" "Data.Int.Int16",           pgType ["float", "double precision", "float8", "pg_catalog.float8"] "ghc-prim" "GHC.Types.Double",           pgType ["real", "float4", "pg_catalog.float4"] "ghc-prim" "GHC.Types.Float",-          pgType ["numeric", "pg_catalog.numeric", "money"] "scientific" "Data.Scientific.Scientific",+          -- hasql has no codec for "money", so leave it unresolved there: the+          -- user gets a "could not resolve type" error pointing at the column+          -- instead of a decoder that fails at runtime. Same for "name" below.+          pgType (["numeric", "pg_catalog.numeric"] <> unlessHasql ["money"]) "scientific" "Data.Scientific.Scientific",           pgType ["boolean", "bool", "pg_catalog.bool"] "ghc-prim" "GHC.Types.Bool",           pgType ["json", "pg_catalog.json"] "aeson" "Data.Aeson.Value",           pgType ["jsonb", "pg_catalog.jsonb"] "aeson" "Data.Aeson.Value",           pgBinary ["bytea", "blob", "pg_catalog.bytea"],-          pgType ["text", "pg_catalog.varchar", "pg_catalog.bpchar", "string", "citext", "name"] "text" "Data.Text.Text"+          pgType+            (["text", "pg_catalog.varchar", "pg_catalog.bpchar", "string", "citext"] <> unlessHasql ["name"])+            "text"+            "Data.Text.Text"         ]   where+    unlessHasql types =+      case backend of+        Just Hasql -> []+        _ -> types+     columnType :: Text     columnType =       columnDataType (column ^. #type')@@ -728,7 +784,18 @@       | otherwise =           Nothing +    -- postgresql-simple needs the Binary wrapper to send/receive bytea in the+    -- binary format; hasql's bytea codec works on a plain ByteString.     pgBinary pgTypes+      | columnType `elem` pgTypes,+        Just Hasql <- backend =+          Just $+            pure+              HaskellType+                { package = Just "bytestring",+                  module' = Just "Data.ByteString",+                  name = Just "Data.ByteString.ByteString"+                }       | columnType `elem` pgTypes =           Just $             HaskellType@@ -754,7 +821,7 @@ -- understand only. mangleQuery :: Text -> Text mangleQuery =-  unQuestionmark . dollarsToQuestionmark+  unQuestionmark . numberedQuestionmarksToQuestionmark . dollarsToQuestionmark   where     -- Replace '$x' with '?'     dollarsToQuestionmark =@@ -762,6 +829,19 @@         . map (Data.Text.dropWhile Data.Char.isDigit)         . Data.Text.splitOn "$" +    -- Normalize numbered '?x' placeholders to a bare '?'. sqlc emits these for+    -- @sqlc.arg@ (e.g. @?1@, @?2@) but sqlite-simple only understands the+    -- positional '?' and fails to parse the numbered form. Only the digits+    -- immediately following a '?' are dropped; the text before the first '?'+    -- is left untouched.+    numberedQuestionmarksToQuestionmark input =+      case Data.Text.splitOn "?" input of+        (x : xs) ->+          Data.Text.intercalate "?"+            (x : map (Data.Text.dropWhile Data.Char.isDigit) xs)+        [] ->+          input+     -- Replace '(?)' with '?'     -- Due to pretty printing and formatting it could look like     --@@ -785,16 +865,21 @@ -- -- PostgreSQL uses numbered placeholders (@$1@, @$2@) which may repeat or appear -- out of order, so we read the explicit numbers. SQLite uses positional @?@--- placeholders with no number; for the @sqlite@ engine we emit sequential--- indices @[1..n]@ matching the parameter list order.+-- placeholders, but sqlc emits numbered @?N@ placeholders for @sqlc.arg@ (which+-- may likewise repeat or be reordered); when present we read those numbers.+-- For bare positional @?@ placeholders we fall back to sequential indices+-- @[1..n]@ matching the parameter list order. ----- The @?@ fallback is deliberately scoped to SQLite: PostgreSQL always uses+-- The @?@ handling is deliberately scoped to SQLite: PostgreSQL always uses -- @$n@, and the MySQL path is left on the numbered behaviour to avoid changing -- it here. Only SQLite needs (and gets) the positional-@?@ handling. queryParamBindings :: Text -> Text -> [Int] queryParamBindings engine query =   case numbered of-    [] | engine == "sqlite" -> [1 .. Data.Text.count "?" query]+    [] | engine == "sqlite" ->+      case questionmarkNumbered of+        Just bindings -> bindings+        Nothing -> [1 .. Data.Text.count "?" query]     bindings -> bindings   where     numbered =@@ -802,3 +887,222 @@         [ readMaybe (toString (Data.Text.takeWhile Data.Char.isDigit x))           | x <- Data.Text.splitOn "$" query         ]++    -- One segment per '?' occurrence (dropping the text before the first '?').+    -- If any occurrence carries an explicit number (@?N@), read the numbers,+    -- using the positional index as a fallback for any bare '?'. If none are+    -- numbered, return 'Nothing' so the caller uses the sequential default.+    questionmarkNumbered =+      case drop 1 (Data.Text.splitOn "?" query) of+        segments+          | any (not . Data.Text.null . takeDigits) segments ->+              Just (zipWith bindingFor [1 ..] segments)+          | otherwise ->+              Nothing+      where+        takeDigits = Data.Text.takeWhile Data.Char.isDigit+        bindingFor index segment =+          fromMaybe index (readMaybe (toString (takeDigits segment)))++-- | The hasql @(encoder, decoder)@ pair to use for a column.+--+-- An override that carries @hasql_encoder@ / @hasql_decoder@ decides the codec+-- outright. Failing that, an override that chose the column's Haskell type also+-- decides its codec, so we go through the generated @ToField@ / @FromField@+-- classes, which the user can instantiate for whatever type they picked. Only+-- when sqlc-hs typed the column itself do we know the codec, and then we take+-- it from the SQL type.+hasqlColumnCodec ::+  -- | The override that matched the column, if any+  Maybe Override ->+  Proto.Protos.Codegen.Column ->+  (Text, Text)+hasqlColumnCodec override column =+  ( wrapHasqlEncoder column encoder,+    wrapHasqlDecoder column decoder+  )+  where+    (encoder, decoder) =+      case override of+        Just Override {hasqlEncoder = Just encoder, hasqlDecoder = Just decoder} ->+          (encoder, decoder)+        Just _ ->+          classCodec+        Nothing ->+          fromMaybe classCodec (hasqlValueCodec (columnDataType (column ^. #type')))++    classCodec =+      ("toField", "fromField")++-- | hasql's codec for a PostgreSQL type, as @(encoder, decoder)@ expressions.+--+-- Unlike postgresql-simple, hasql picks a codec per SQL type rather than per+-- Haskell type, and from 1.10 on it rejects a result whose column OID doesn't+-- match the decoder. @text@, @varchar@ and @bpchar@ all map to 'Text' but need+-- three different decoders, so the choice has to be made here, from the type+-- sqlc reported, and not from a class keyed on the Haskell type.+hasqlValueCodec ::+  -- | Database type, as 'columnDataType' renders it+  Text ->+  Maybe (Text, Text)+hasqlValueCodec databaseType =+  fmap codec $+    find+      (\(databaseTypes, _codec) -> databaseType `elem` databaseTypes)+      [ (["serial", "serial4", "pg_catalog.serial4", "integer", "int", "int4", "pg_catalog.int4"], "int4"),+        (["bigserial", "serial8", "pg_catalog.serial8", "bigint", "int8", "pg_catalog.int8"], "int8"),+        (["smallserial", "serial2", "pg_catalog.serial2", "smallint", "int2", "pg_catalog.int2"], "int2"),+        (["float", "double precision", "float8", "pg_catalog.float8"], "float8"),+        (["real", "float4", "pg_catalog.float4"], "float4"),+        (["numeric", "pg_catalog.numeric"], "numeric"),+        (["boolean", "bool", "pg_catalog.bool"], "bool"),+        (["json", "pg_catalog.json"], "json"),+        (["jsonb", "pg_catalog.jsonb"], "jsonb"),+        (["bytea", "blob", "pg_catalog.bytea"], "bytea"),+        (["text", "string", "pg_catalog.text"], "text"),+        (["varchar", "pg_catalog.varchar"], "varchar"),+        (["bpchar", "pg_catalog.bpchar"], "bpchar"),+        (["citext"], "citext")+      ]+  where+    codec (_databaseTypes, name) =+      ("Hasql.Encoders." <> name, "Hasql.Decoders." <> name)++-- | Lift a hasql value encoder to the parameter encoder for a column, applying+-- the same nullability and array wrapping that 'applyNullable' and+-- 'applyArrayLike' applied to the column's Haskell type.+wrapHasqlEncoder :: Proto.Protos.Codegen.Column -> Text -> Text+wrapHasqlEncoder =+  wrapHasqlValue "Hasql.Encoders" "foldableArray"++-- | 'wrapHasqlEncoder' for decoders. Array columns decode into a 'Vector', which+-- is what 'wrapVector' gave them as a Haskell type.+wrapHasqlDecoder :: Proto.Protos.Codegen.Column -> Text -> Text+wrapHasqlDecoder =+  wrapHasqlValue "Hasql.Decoders" "vectorArray"++wrapHasqlValue :: Text -> Text -> Proto.Protos.Codegen.Column -> Text -> Text+wrapHasqlValue module' arrayCodec column value =+  qualified nullability <> " " <> wrapParenthesis arrayed+  where+    qualified name =+      module' <> "." <> name++    nullability+      | column ^. #notNull = "nonNullable"+      | otherwise = "nullable"++    arrayed+      | column ^. #isArray || column ^. #isSqlcSlice =+          qualified arrayCodec+            <> " "+            <> wrapParenthesis (qualified "nonNullable" <> " " <> wrapParenthesis value)+      | otherwise =+          value++-- | Rewrite the @IN@ / @NOT IN@ operators over @sqlc.slice@ parameters into+-- their array equivalents.+--+-- PostgreSQL's @IN@ takes a syntactic list of values, not an array, which is+-- why postgresql-simple expands a slice into as many placeholders as there are+-- elements. hasql binds one parameter per placeholder and cannot do that, so+-- the array operators have to be used instead:+--+--   * @x IN ($1)@ becomes @x = ANY ($1)@+--   * @x NOT IN ($1)@ becomes @x <> ALL ($1)@+--+-- Returns 'Left' when a slice parameter isn't in a shape we recognise, rather+-- than emitting SQL that only fails once it reaches the server.+rewriteSlices ::+  -- | The numbers of the parameters that are slices+  [Int] ->+  Text ->+  Either Text Text+rewriteSlices slices sql+  | null slices =+      Right sql+  | otherwise =+      go mempty sql+  where+    go acc input =+      case Data.Text.breakOn "$" input of+        (before, rest)+          | Just rest <- Data.Text.stripPrefix "$" rest,+            (digits, after) <- Data.Text.span Data.Char.isDigit rest,+            Just number <- readMaybe (toString digits),+            number `elem` slices ->+              case rewriteSlice (acc <> before) digits after of+                Left errorMessage ->+                  Left errorMessage+                Right (acc, after) ->+                  go acc after+          | Just rest <- Data.Text.stripPrefix "$" rest,+            (digits, after) <- Data.Text.span Data.Char.isDigit rest ->+              go (acc <> before <> "$" <> digits) after+          | otherwise ->+              Right (acc <> before)++    -- 'before' is everything preceding the placeholder, 'after' everything+    -- following it. Both get rewritten: the operator sits in front of the+    -- placeholder, the closing parenthesis (if any) behind it.+    rewriteSlice before digits after = do+      let placeholder = "$" <> digits++          -- sqlc marks slices with a comment for the engines that use+          -- positional placeholders. Drop it, it has served its purpose.+          withoutMarker = stripSliceMarker before++          (withoutParenthesis, parenthesised) =+            case Data.Text.stripSuffix "(" (Data.Text.stripEnd withoutMarker) of+              Just before -> (before, True)+              Nothing -> (withoutMarker, False)++      after <-+        if parenthesised+          then+            whenNothing+              (Data.Text.stripPrefix ")" (Data.Text.stripStart after))+              (Left (unsupported placeholder))+          else pure after++      beforeIn <-+        whenNothing+          (stripKeywordSuffix "IN" (Data.Text.stripEnd withoutParenthesis))+          (Left (unsupported placeholder))++      pure $+        case stripKeywordSuffix "NOT" (Data.Text.stripEnd beforeIn) of+          Just beforeNot ->+            (Data.Text.stripEnd beforeNot <> " <> ALL (" <> placeholder <> ")", after)+          Nothing ->+            (Data.Text.stripEnd beforeIn <> " = ANY (" <> placeholder <> ")", after)++    unsupported placeholder =+      "The slice parameter "+        <> placeholder+        <> " is not used with IN or NOT IN. hasql binds a slice as a single\+           \ array parameter, which PostgreSQL only accepts with the array\+           \ operators; write the comparison as \"= ANY(sqlc.arg(...)::type[])\"\+           \ in your SQL instead of using sqlc.slice."++-- | Strip a @/*SLICE:name*/@ marker off the end of the text, if there is one.+stripSliceMarker :: Text -> Text+stripSliceMarker input = fromMaybe input $ do+  comment <- Data.Text.stripSuffix "*/" (Data.Text.stripEnd input)+  let (before, marker) = Data.Text.breakOnEnd "/*" comment+  guard ("SLICE:" `Data.Text.isPrefixOf` marker)+  Data.Text.stripSuffix "/*" before++-- | Strip a keyword off the end of the text, case insensitively, requiring it+-- to be a word of its own rather than the tail of an identifier.+stripKeywordSuffix :: Text -> Text -> Maybe Text+stripKeywordSuffix keyword input = do+  guard (Data.Text.length input >= Data.Text.length keyword)+  let (before, suffix) =+        Data.Text.splitAt (Data.Text.length input - Data.Text.length keyword) input+  guard (Data.Text.toUpper suffix == keyword)+  guard (maybe True (not . isIdentifierChar . snd) (Data.Text.unsnoc before))+  pure before+  where+    isIdentifierChar c =+      Data.Char.isAlphaNum c || c == '_'
+ templates/internal.hasql.hs.jinja view
@@ -0,0 +1,406 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE TypeFamilies #-}+module {{ moduleName }} (+    Query(..),+    Params,+    Result,+    {{ moduleName }}.Enum,++    -- * Codecs+    ToRow(..),+    FromRow(..),+    ToField(..),+    FromField(..),+    statement,++    -- * :execResult+    ExecResult(..),+    execResult,+    execResultSession,++    -- * :exec+    exec,+    execSession,++    -- * :execrows+    execRows,+    execRowsSession,++    -- * :one+    queryOne,+    queryOneSession,++    -- * :many+    queryMany,+    queryManySession,+    fold,+    foldSession,++    -- * :copyfrom+    execMany,+    execManySession,++    -- * Reexports+    Hasql.Connection.Connection,+    Hasql.Session.Session,+    RunnerError,+    Hasql.Errors.SessionError,+  ) where++import Data.Foldable (Foldable)+import Data.Vector (Vector)+import GHC.TypeLits (Symbol)+import qualified Data.Aeson+import qualified Data.ByteString+import qualified Data.Foldable+import qualified Data.Int+import qualified Data.Scientific+import qualified Data.Text+import qualified Data.Time+import qualified Data.UUID+import qualified Data.Vector+import qualified Hasql.Connection+import qualified Hasql.Decoders+import qualified Hasql.Encoders+import qualified Hasql.Errors+import qualified Hasql.Pipeline+import qualified Hasql.Session+import qualified Hasql.Statement++-- | What a runner reports when it fails.+--+-- hasql 2.1 replaced the single session-error type that+-- 'Hasql.Connection.use' returned with @UseError@, which distinguishes a+-- statement that failed on a live connection (@SessionUseError@, wrapping the+-- 'Hasql.Errors.SessionError' the old type expressed) from a connection that is+-- gone and has already been closed (@ConnectionUseError@).+--+-- Named through this alias so the generated module compiles against both, and+-- so a call site that only passes the error along does not have to care.+#if MIN_VERSION_hasql(2,1,0)+type RunnerError = Hasql.Errors.UseError+#else+type RunnerError = Hasql.Errors.SessionError+#endif++-- | The SQL of a query, with PostgreSQL's positional @$1@, @$2@ placeholders+-- left as sqlc emitted them.+newtype Query (name :: Symbol) (command :: Symbol)+  = Query Data.Text.Text++data family Params (name :: Symbol)++data family Result (name :: Symbol)++data family Enum (name :: Symbol)++-- | The parameter encoder of a query. hasql has no such class of its own --+-- encoders are plain values -- so sqlc-hs declares one and generates an+-- instance per query.+class ToRow a where+  toRow :: Hasql.Encoders.Params a++-- | The row decoder of a query's result. The counterpart of 'ToRow'.+class FromRow a where+  fromRow :: Hasql.Decoders.Row a++-- | The codec of a single value.+--+-- sqlc-hs takes hasql's codec from the SQL type for every column it typed+-- itself, because hasql checks column types when decoding and @text@, @varchar@+-- and @bpchar@ need three different decoders. Columns typed through an+-- @overrides@ entry go through this class instead, so a custom type only needs+-- an instance here:+--+-- @+-- instance ToField MyId where+--   toField = Data.Functor.Contravariant.contramap unMyId toField+-- @+--+-- An override can also name the codecs directly, with the @hasql_encoder@ and+-- @hasql_decoder@ keys, in which case no instance is needed.+class ToField a where+  toField :: Hasql.Encoders.Value a++-- | The counterpart of 'ToField'.+--+-- @+-- instance FromField MyId where+--   fromField = fmap MyId fromField+-- @+class FromField a where+  fromField :: Hasql.Decoders.Value a++instance ToField Bool where+  toField = Hasql.Encoders.bool++instance ToField Data.Int.Int16 where+  toField = Hasql.Encoders.int2++instance ToField Data.Int.Int32 where+  toField = Hasql.Encoders.int4++instance ToField Data.Int.Int64 where+  toField = Hasql.Encoders.int8++instance ToField Float where+  toField = Hasql.Encoders.float4++instance ToField Double where+  toField = Hasql.Encoders.float8++instance ToField Data.Scientific.Scientific where+  toField = Hasql.Encoders.numeric++instance ToField Char where+  toField = Hasql.Encoders.char++instance ToField Data.Text.Text where+  toField = Hasql.Encoders.text++instance ToField Data.ByteString.ByteString where+  toField = Hasql.Encoders.bytea++instance ToField Data.UUID.UUID where+  toField = Hasql.Encoders.uuid++instance ToField Data.Time.Day where+  toField = Hasql.Encoders.date++instance ToField Data.Time.LocalTime where+  toField = Hasql.Encoders.timestamp++instance ToField Data.Time.UTCTime where+  toField = Hasql.Encoders.timestamptz++instance ToField Data.Time.TimeOfDay where+  toField = Hasql.Encoders.time++instance ToField (Data.Time.TimeOfDay, Data.Time.TimeZone) where+  toField = Hasql.Encoders.timetz++instance ToField Data.Time.DiffTime where+  toField = Hasql.Encoders.interval++instance ToField Data.Aeson.Value where+  toField = Hasql.Encoders.jsonb++instance (ToField a) => ToField (Data.Vector.Vector a) where+  toField = Hasql.Encoders.foldableArray (Hasql.Encoders.nonNullable toField)++instance (ToField a) => ToField [a] where+  toField = Hasql.Encoders.foldableArray (Hasql.Encoders.nonNullable toField)++instance FromField Bool where+  fromField = Hasql.Decoders.bool++instance FromField Data.Int.Int16 where+  fromField = Hasql.Decoders.int2++instance FromField Data.Int.Int32 where+  fromField = Hasql.Decoders.int4++instance FromField Data.Int.Int64 where+  fromField = Hasql.Decoders.int8++instance FromField Float where+  fromField = Hasql.Decoders.float4++instance FromField Double where+  fromField = Hasql.Decoders.float8++instance FromField Data.Scientific.Scientific where+  fromField = Hasql.Decoders.numeric++instance FromField Char where+  fromField = Hasql.Decoders.char++instance FromField Data.Text.Text where+  fromField = Hasql.Decoders.text++instance FromField Data.ByteString.ByteString where+  fromField = Hasql.Decoders.bytea++instance FromField Data.UUID.UUID where+  fromField = Hasql.Decoders.uuid++instance FromField Data.Time.Day where+  fromField = Hasql.Decoders.date++instance FromField Data.Time.LocalTime where+  fromField = Hasql.Decoders.timestamp++instance FromField Data.Time.UTCTime where+  fromField = Hasql.Decoders.timestamptz++instance FromField Data.Time.TimeOfDay where+  fromField = Hasql.Decoders.time++instance FromField (Data.Time.TimeOfDay, Data.Time.TimeZone) where+  fromField = Hasql.Decoders.timetz++instance FromField Data.Time.DiffTime where+  fromField = Hasql.Decoders.interval++instance FromField Data.Aeson.Value where+  fromField = Hasql.Decoders.jsonb++instance (FromField a) => FromField (Data.Vector.Vector a) where+  fromField = Hasql.Decoders.vectorArray (Hasql.Decoders.nonNullable fromField)++instance (FromField a) => FromField [a] where+  fromField = Hasql.Decoders.listArray (Hasql.Decoders.nonNullable fromField)++data ExecResult = ExecResult+  { rowsAffected :: !Data.Int.Int64+  }++-- | The statement a query and a result decoder make up. Use it to run a query+-- inside a 'Hasql.Pipeline.Pipeline'.+statement ::+  (ToRow (Params name)) =>+  Query name command ->+  Hasql.Decoders.Result result ->+  Hasql.Statement.Statement (Params name) result+statement (Query sql) decoder =+  Hasql.Statement.preparable sql toRow decoder++exec ::+  (ToRow (Params name)) =>+  Hasql.Connection.Connection ->+  Query name ":exec" ->+  Params name ->+  IO (Either RunnerError ())+exec connection query params =+  Hasql.Connection.use connection (execSession query params)++execSession ::+  (ToRow (Params name)) =>+  Query name ":exec" ->+  Params name ->+  Hasql.Session.Session ()+execSession query params =+  Hasql.Session.statement params (statement query Hasql.Decoders.noResult)++execRows ::+  (ToRow (Params name)) =>+  Hasql.Connection.Connection ->+  Query name ":execrows" ->+  Params name ->+  IO (Either RunnerError Data.Int.Int64)+execRows connection query params =+  Hasql.Connection.use connection (execRowsSession query params)++execRowsSession ::+  (ToRow (Params name)) =>+  Query name ":execrows" ->+  Params name ->+  Hasql.Session.Session Data.Int.Int64+execRowsSession query params =+  Hasql.Session.statement params (statement query Hasql.Decoders.rowsAffected)++execResult ::+  (ToRow (Params name)) =>+  Hasql.Connection.Connection ->+  Query name ":execresult" ->+  Params name ->+  IO (Either RunnerError ExecResult)+execResult connection query params =+  Hasql.Connection.use connection (execResultSession query params)++execResultSession ::+  (ToRow (Params name)) =>+  Query name ":execresult" ->+  Params name ->+  Hasql.Session.Session ExecResult+execResultSession query params = do+  rowsAffected <- Hasql.Session.statement params (statement query Hasql.Decoders.rowsAffected)+  pure ExecResult {+    rowsAffected+  }++queryOne ::+  (ToRow (Params name), FromRow (Result name)) =>+  Hasql.Connection.Connection ->+  Query name ":one" ->+  Params name ->+  IO (Either RunnerError (Maybe (Result name)))+queryOne connection query params =+  Hasql.Connection.use connection (queryOneSession query params)++queryOneSession ::+  (ToRow (Params name), FromRow (Result name)) =>+  Query name ":one" ->+  Params name ->+  Hasql.Session.Session (Maybe (Result name))+queryOneSession query params =+  Hasql.Session.statement params (statement query (Hasql.Decoders.rowMaybe fromRow))++queryMany ::+  (ToRow (Params name), FromRow (Result name)) =>+  Hasql.Connection.Connection ->+  Query name ":many" ->+  Params name ->+  IO (Either RunnerError (Vector (Result name)))+queryMany connection query params =+  Hasql.Connection.use connection (queryManySession query params)++queryManySession ::+  (ToRow (Params name), FromRow (Result name)) =>+  Query name ":many" ->+  Params name ->+  Hasql.Session.Session (Vector (Result name))+queryManySession query params =+  Hasql.Session.statement params (statement query (Hasql.Decoders.rowVector fromRow))++-- | Note that hasql folds a result purely, so unlike the postgresql-simple+-- backend the step function is not in 'IO'.+fold ::+  (ToRow (Params name), FromRow (Result name)) =>+  Hasql.Connection.Connection ->+  Query name ":many" ->+  Params name ->+  a ->+  (a -> Result name -> a) ->+  IO (Either RunnerError a)+fold connection query params initial step =+  Hasql.Connection.use connection (foldSession query params initial step)+{-# INLINABLE fold #-}++foldSession ::+  (ToRow (Params name), FromRow (Result name)) =>+  Query name ":many" ->+  Params name ->+  a ->+  (a -> Result name -> a) ->+  Hasql.Session.Session a+foldSession query params initial step =+  Hasql.Session.statement params (statement query (Hasql.Decoders.foldlRows step initial fromRow))+{-# INLINABLE foldSession #-}++-- | Runs the query once per set of parameters, pipelined into a single+-- round trip, and returns the total number of rows affected.+execMany ::+  (ToRow (Params name), Foldable f) =>+  Hasql.Connection.Connection ->+  Query name ":copyfrom" ->+  f (Params name) ->+  IO (Either RunnerError Data.Int.Int64)+execMany connection query params =+  Hasql.Connection.use connection (execManySession query params)++execManySession ::+  (ToRow (Params name), Foldable f) =>+  Query name ":copyfrom" ->+  f (Params name) ->+  Hasql.Session.Session Data.Int.Int64+execManySession query params =+  Hasql.Session.pipeline+    (fmap sum (traverse pipelined (Data.Foldable.toList params)))+  where+    pipelined param =+      Hasql.Pipeline.statement param (statement query Hasql.Decoders.rowsAffected)
templates/query.hs.jinja view
@@ -15,6 +15,11 @@ import qualified Database.PostgreSQL.Simple.ToField import qualified Database.PostgreSQL.Simple.ToRow {% endif %}+{% if generateHasql %}+import {{ internalModuleName }} (Query(..), Enum, Params, Result, ToRow(..), FromRow(..), ToField(..), FromField(..))+import qualified Hasql.Decoders+import qualified Hasql.Encoders+{% endif %} {% if generateSqlite %} import {{ internalModuleName }} (Query(..), Enum, Params, Result) import qualified Database.SQLite.Simple.FromRow@@ -71,6 +76,24 @@     pure {{ haskellResultName }}     {% for column in resultColumns %}       <*> Database.PostgreSQL.Simple.FromRow.field+    {% endfor %}+{% endif %}+{% if generateHasql %}+instance ToRow (Params {{ escapedQueryName }}) where+  {-# INLINE toRow #-}+  toRow =+    mconcat+      [ {% for param in encoderColumns %}+      Data.Functor.Contravariant.contramap (\{{ haskellParamsName }}{..} -> {{ param.value.name }}) (Hasql.Encoders.param ({{ param.value.encoder }})){% if !param.last %}, {% endif %}+      {% endfor %}+      ]++instance FromRow (Result {{ escapedQueryName }}) where+  {-# INLINE fromRow #-}+  fromRow =+    pure {{ haskellResultName }}+    {% for column in resultColumns %}+      <*> Hasql.Decoders.column ({{ column.value.decoder }})     {% endfor %} {% endif %} 
templates/types.hs.jinja view
@@ -18,6 +18,10 @@ import qualified Database.PostgreSQL.Simple.ToField import qualified Database.PostgreSQL.Simple.ToRow {% endif %}+{% if generateHasql %}+import qualified Hasql.Decoders+import qualified Hasql.Encoders+{% endif %} {% if generateSqlite %} import qualified Database.SQLite.Simple.FromRow import qualified Database.SQLite.Simple.ToField@@ -57,6 +61,24 @@       {% endfor %}       Just value -> Database.PostgreSQL.Simple.FromField.returnError Database.PostgreSQL.Simple.FromField.ConversionFailed field (show value)       Nothing -> Database.PostgreSQL.Simple.FromField.returnError Database.PostgreSQL.Simple.FromField.UnexpectedNull field ""+{% endif %}+{% if generateHasql %}+instance ToField (Enum {{ enum.value.escapedEnumName }}) where+  toField =+    Hasql.Encoders.enum {{ enum.value.escapedEnumSchema }} {{ enum.value.escapedEnumName }} $ \x ->+      case x of+        {% for value in enum.value.values %}+        {{ value.value.haskellConstructorName }} -> {{ value.value.escapedEnumValue }}+        {% endfor %}++instance FromField (Enum {{ enum.value.escapedEnumName }}) where+  fromField =+    Hasql.Decoders.enum {{ enum.value.escapedEnumSchema }} {{ enum.value.escapedEnumName }} $ \x ->+      case x of+        {% for value in enum.value.values %}+        {{ value.value.escapedEnumValue }} -> Prelude.Just {{ value.value.haskellConstructorName }}+        {% endfor %}+        _ -> Prelude.Nothing {% endif %}  {% endfor %}
+ test/golden/hasql-features.input view
@@ -0,0 +1,212 @@+settings {+  engine: "postgresql"+}++catalog {+  default_schema: "public"+  schemas {+    name: "public"+    enums {+      name: "organization_role"+      vals: "owner"+      vals: "admin"+      vals: "member"+    }+  }+}++queries {+  text: "SELECT id FROM users WHERE $1::TEXT IS NULL OR $1::TEXT = users.name;"+  name: "FindUserByName"+  cmd: ":many"+  filename: "query/users.sql"+  columns {+    name: "id"+    not_null: true+    type {+      name: "int"+    }+  }+  params {+    number: 1+    column {+      name: "name"+      not_null: true+      type {+        name: "text"+      }+    }+  }+}++queries {+  text: "SELECT id FROM users WHERE name IN ($1) AND email NOT IN ($2) AND age > $3;"+  name: "FindUsers"+  cmd: ":many"+  filename: "query/users.sql"+  columns {+    name: "id"+    not_null: true+    type {+      name: "int"+    }+  }+  params {+    number: 1+    column {+      name: "names"+      not_null: true+      is_sqlc_slice: true+      type {+        name: "text"+      }+    }+  }+  params {+    number: 2+    column {+      name: "emails"+      not_null: true+      is_sqlc_slice: true+      type {+        name: "text"+      }+    }+  }+  params {+    number: 3+    column {+      name: "age"+      not_null: true+      type {+        name: "int"+      }+    }+  }+}++queries {+  text: "SELECT tags, labels FROM posts WHERE tags && $1;"+  name: "FindPosts"+  cmd: ":many"+  filename: "query/posts.sql"+  columns {+    name: "tags"+    not_null: true+    is_array: true+    type {+      name: "text"+    }+  }+  columns {+    name: "labels"+    is_array: true+    type {+      name: "pg_catalog.varchar"+    }+  }+  params {+    number: 1+    column {+      name: "tags"+      not_null: true+      is_array: true+      type {+        name: "text"+      }+    }+  }+}++queries {+  text: "SELECT role, previous_role FROM members WHERE role = $1;"+  name: "FindMembers"+  cmd: ":many"+  filename: "query/members.sql"+  columns {+    name: "role"+    not_null: true+    type {+      name: "organization_role"+    }+  }+  columns {+    name: "previous_role"+    type {+      name: "organization_role"+    }+  }+  params {+    number: 1+    column {+      name: "role"+      not_null: true+      type {+        name: "organization_role"+      }+    }+  }+}++queries {+  text: "SELECT id, created_at, updated_at, legacy_at FROM events WHERE id = $1 AND legacy_at > $2;"+  name: "GetEvent"+  cmd: ":one"+  filename: "query/events.sql"+  columns {+    name: "id"+    not_null: true+    type {+      name: "uuid"+    }+  }+  columns {+    name: "created_at"+    not_null: true+    type {+      name: "pg_catalog.timestamptz"+    }+  }+  columns {+    name: "updated_at"+    type {+      name: "pg_catalog.timestamptz"+    }+  }+  columns {+    name: "legacy_at"+    not_null: true+    type {+      name: "pg_catalog.timestamp"+    }+  }+  params {+    number: 1+    column {+      name: "id"+      not_null: true+      type {+        name: "uuid"+      }+    }+  }+  params {+    number: 2+    column {+      name: "since"+      not_null: true+      type {+        name: "pg_catalog.timestamp"+      }+    }+  }+}++queries {+  text: "DELETE FROM users;"+  name: "DeleteUsers"+  cmd: ":exec"+  filename: "query/users.sql"+}++plugin_options: "{\"cabal_package_name\":\"hasql-features\",\"driver\":\"hasql\",\"overrides\":[{\"db_type\":\"pg_catalog.timestamptz\",\"haskell_type\":{\"package\":\"time\",\"module\":\"Data.Time\",\"type\":\"Data.Time.UTCTime\"}},{\"db_type\":\"pg_catalog.timestamptz\",\"nullable\":true,\"haskell_type\":{\"package\":\"time\",\"module\":\"Data.Time\",\"type\":\"Maybe Data.Time.UTCTime\"}},{\"db_type\":\"uuid\",\"haskell_type\":{\"package\":\"uuid\",\"module\":\"Data.UUID\",\"type\":\"Data.UUID.UUID\"}},{\"db_type\":\"pg_catalog.timestamp\",\"haskell_type\":{\"package\":\"time\",\"module\":\"Data.Time\",\"type\":\"Data.Time.UTCTime\"},\"hasql_encoder\":\"Data.Functor.Contravariant.contramap (Data.Time.utcToLocalTime Data.Time.utc) Hasql.Encoders.timestamp\",\"hasql_decoder\":\"fmap (Data.Time.localTimeToUTC Data.Time.utc) Hasql.Decoders.timestamp\"}]}"
+ test/golden/hasql-features/Queries.hs view
@@ -0,0 +1,15 @@+module Queries+  ( module Queries.Internal,+    module Queries.Types,+    module Queries+  )+where++import Queries.Internal+import Queries.Types+import Queries.FindUserByName as Queries+import Queries.FindUsers as Queries+import Queries.FindPosts as Queries+import Queries.FindMembers as Queries+import Queries.GetEvent as Queries+import Queries.DeleteUsers as Queries
+ test/golden/hasql-features/Queries/DeleteUsers.hs view
@@ -0,0 +1,40 @@+{- This file was auto-generated from query/users.sql by sqlc-hs. -}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+module Queries.DeleteUsers where++import Queries.Internal (Query(..), Enum, Params, Result, ToRow(..), FromRow(..), ToField(..), FromField(..))+import qualified Hasql.Decoders+import qualified Hasql.Encoders++import qualified Data.Foldable+import qualified Data.Functor.Contravariant++query_DeleteUsers :: Query "DeleteUsers" ":exec"+query_DeleteUsers = Query "DELETE FROM users;"++data instance Params "DeleteUsers" = Params_DeleteUsers+  {+  }++data instance Result "DeleteUsers" = Result_DeleteUsers+  {+  }++instance ToRow (Params "DeleteUsers") where+  {-# INLINE toRow #-}+  toRow =+    mconcat+      [       ]++instance FromRow (Result "DeleteUsers") where+  {-# INLINE fromRow #-}+  fromRow =+    pure Result_DeleteUsers++
+ test/golden/hasql-features/Queries/FindMembers.hs view
@@ -0,0 +1,49 @@+{- This file was auto-generated from query/members.sql by sqlc-hs. -}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+module Queries.FindMembers where++import Queries.Internal (Query(..), Enum, Params, Result, ToRow(..), FromRow(..), ToField(..), FromField(..))+import qualified Hasql.Decoders+import qualified Hasql.Encoders++import qualified Queries.Types+import qualified GHC.Base+import qualified Data.Foldable+import qualified Data.Functor.Contravariant++query_FindMembers :: Query "FindMembers" ":many"+query_FindMembers = Query "SELECT role, previous_role FROM members WHERE role = $1;"++data instance Params "FindMembers" = Params_FindMembers+  {+    role :: (Queries.Types.Enum "organization_role")+  }++data instance Result "FindMembers" = Result_FindMembers+  {+    role :: !((Queries.Types.Enum "organization_role")),+    previous_role :: !(GHC.Base.Maybe ((Queries.Types.Enum "organization_role")))+  }++instance ToRow (Params "FindMembers") where+  {-# INLINE toRow #-}+  toRow =+    mconcat+      [ +      Data.Functor.Contravariant.contramap (\Params_FindMembers{..} -> role) (Hasql.Encoders.param (Hasql.Encoders.nonNullable toField))+      ]++instance FromRow (Result "FindMembers") where+  {-# INLINE fromRow #-}+  fromRow =+    pure Result_FindMembers+      <*> Hasql.Decoders.column (Hasql.Decoders.nonNullable fromField)+      <*> Hasql.Decoders.column (Hasql.Decoders.nullable fromField)++
+ test/golden/hasql-features/Queries/FindPosts.hs view
@@ -0,0 +1,50 @@+{- This file was auto-generated from query/posts.sql by sqlc-hs. -}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+module Queries.FindPosts where++import Queries.Internal (Query(..), Enum, Params, Result, ToRow(..), FromRow(..), ToField(..), FromField(..))+import qualified Hasql.Decoders+import qualified Hasql.Encoders++import qualified Data.Text+import qualified Data.Vector+import qualified GHC.Base+import qualified Data.Foldable+import qualified Data.Functor.Contravariant++query_FindPosts :: Query "FindPosts" ":many"+query_FindPosts = Query "SELECT tags, labels FROM posts WHERE tags && $1;"++data instance Params "FindPosts" = Params_FindPosts+  {+    tags :: Data.Vector.Vector Data.Text.Text+  }++data instance Result "FindPosts" = Result_FindPosts+  {+    tags :: !(Data.Vector.Vector Data.Text.Text),+    labels :: !(GHC.Base.Maybe (Data.Vector.Vector Data.Text.Text))+  }++instance ToRow (Params "FindPosts") where+  {-# INLINE toRow #-}+  toRow =+    mconcat+      [ +      Data.Functor.Contravariant.contramap (\Params_FindPosts{..} -> tags) (Hasql.Encoders.param (Hasql.Encoders.nonNullable (Hasql.Encoders.foldableArray (Hasql.Encoders.nonNullable Hasql.Encoders.text))))+      ]++instance FromRow (Result "FindPosts") where+  {-# INLINE fromRow #-}+  fromRow =+    pure Result_FindPosts+      <*> Hasql.Decoders.column (Hasql.Decoders.nonNullable (Hasql.Decoders.vectorArray (Hasql.Decoders.nonNullable Hasql.Decoders.text)))+      <*> Hasql.Decoders.column (Hasql.Decoders.nullable (Hasql.Decoders.vectorArray (Hasql.Decoders.nonNullable Hasql.Decoders.varchar)))++
+ test/golden/hasql-features/Queries/FindUserByName.hs view
@@ -0,0 +1,47 @@+{- This file was auto-generated from query/users.sql by sqlc-hs. -}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+module Queries.FindUserByName where++import Queries.Internal (Query(..), Enum, Params, Result, ToRow(..), FromRow(..), ToField(..), FromField(..))+import qualified Hasql.Decoders+import qualified Hasql.Encoders++import qualified Data.Text+import qualified Data.Int+import qualified Data.Foldable+import qualified Data.Functor.Contravariant++query_FindUserByName :: Query "FindUserByName" ":many"+query_FindUserByName = Query "SELECT id FROM users WHERE $1::TEXT IS NULL OR $1::TEXT = users.name;"++data instance Params "FindUserByName" = Params_FindUserByName+  {+    name :: Data.Text.Text+  }++data instance Result "FindUserByName" = Result_FindUserByName+  {+    id :: !(Data.Int.Int32)+  }++instance ToRow (Params "FindUserByName") where+  {-# INLINE toRow #-}+  toRow =+    mconcat+      [ +      Data.Functor.Contravariant.contramap (\Params_FindUserByName{..} -> name) (Hasql.Encoders.param (Hasql.Encoders.nonNullable Hasql.Encoders.text))+      ]++instance FromRow (Result "FindUserByName") where+  {-# INLINE fromRow #-}+  fromRow =+    pure Result_FindUserByName+      <*> Hasql.Decoders.column (Hasql.Decoders.nonNullable Hasql.Decoders.int4)++
+ test/golden/hasql-features/Queries/FindUsers.hs view
@@ -0,0 +1,53 @@+{- This file was auto-generated from query/users.sql by sqlc-hs. -}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+module Queries.FindUsers where++import Queries.Internal (Query(..), Enum, Params, Result, ToRow(..), FromRow(..), ToField(..), FromField(..))+import qualified Hasql.Decoders+import qualified Hasql.Encoders++import qualified Data.Text+import qualified Data.Int+import qualified Data.Foldable+import qualified Data.Functor.Contravariant++query_FindUsers :: Query "FindUsers" ":many"+query_FindUsers = Query "SELECT id FROM users WHERE name = ANY ($1) AND email <> ALL ($2) AND age > $3;"++data instance Params "FindUsers" = Params_FindUsers+  {+    names :: [Data.Text.Text],+    emails :: [Data.Text.Text],+    age :: Data.Int.Int32+  }++data instance Result "FindUsers" = Result_FindUsers+  {+    id :: !(Data.Int.Int32)+  }++instance ToRow (Params "FindUsers") where+  {-# INLINE toRow #-}+  toRow =+    mconcat+      [ +      Data.Functor.Contravariant.contramap (\Params_FindUsers{..} -> names) (Hasql.Encoders.param (Hasql.Encoders.nonNullable (Hasql.Encoders.foldableArray (Hasql.Encoders.nonNullable Hasql.Encoders.text)))), ++      Data.Functor.Contravariant.contramap (\Params_FindUsers{..} -> emails) (Hasql.Encoders.param (Hasql.Encoders.nonNullable (Hasql.Encoders.foldableArray (Hasql.Encoders.nonNullable Hasql.Encoders.text)))), ++      Data.Functor.Contravariant.contramap (\Params_FindUsers{..} -> age) (Hasql.Encoders.param (Hasql.Encoders.nonNullable Hasql.Encoders.int4))+      ]++instance FromRow (Result "FindUsers") where+  {-# INLINE fromRow #-}+  fromRow =+    pure Result_FindUsers+      <*> Hasql.Decoders.column (Hasql.Decoders.nonNullable Hasql.Decoders.int4)++
+ test/golden/hasql-features/Queries/GetEvent.hs view
@@ -0,0 +1,56 @@+{- This file was auto-generated from query/events.sql by sqlc-hs. -}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+module Queries.GetEvent where++import Queries.Internal (Query(..), Enum, Params, Result, ToRow(..), FromRow(..), ToField(..), FromField(..))+import qualified Hasql.Decoders+import qualified Hasql.Encoders++import qualified Data.UUID+import qualified Data.Time+import qualified Data.Foldable+import qualified Data.Functor.Contravariant++query_GetEvent :: Query "GetEvent" ":one"+query_GetEvent = Query "SELECT id, created_at, updated_at, legacy_at FROM events WHERE id = $1 AND legacy_at > $2;"++data instance Params "GetEvent" = Params_GetEvent+  {+    id :: Data.UUID.UUID,+    since :: Data.Time.UTCTime+  }++data instance Result "GetEvent" = Result_GetEvent+  {+    id :: !(Data.UUID.UUID),+    created_at :: !(Data.Time.UTCTime),+    updated_at :: !((Maybe Data.Time.UTCTime)),+    legacy_at :: !(Data.Time.UTCTime)+  }++instance ToRow (Params "GetEvent") where+  {-# INLINE toRow #-}+  toRow =+    mconcat+      [ +      Data.Functor.Contravariant.contramap (\Params_GetEvent{..} -> id) (Hasql.Encoders.param (Hasql.Encoders.nonNullable toField)), ++      Data.Functor.Contravariant.contramap (\Params_GetEvent{..} -> since) (Hasql.Encoders.param (Hasql.Encoders.nonNullable (Data.Functor.Contravariant.contramap (Data.Time.utcToLocalTime Data.Time.utc) Hasql.Encoders.timestamp)))+      ]++instance FromRow (Result "GetEvent") where+  {-# INLINE fromRow #-}+  fromRow =+    pure Result_GetEvent+      <*> Hasql.Decoders.column (Hasql.Decoders.nonNullable fromField)+      <*> Hasql.Decoders.column (Hasql.Decoders.nonNullable fromField)+      <*> Hasql.Decoders.column (Hasql.Decoders.nullable fromField)+      <*> Hasql.Decoders.column (Hasql.Decoders.nonNullable (fmap (Data.Time.localTimeToUTC Data.Time.utc) Hasql.Decoders.timestamp))++
+ test/golden/hasql-features/Queries/Internal.hs view
@@ -0,0 +1,406 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE TypeFamilies #-}+module Queries.Internal (+    Query(..),+    Params,+    Result,+    Queries.Internal.Enum,++    -- * Codecs+    ToRow(..),+    FromRow(..),+    ToField(..),+    FromField(..),+    statement,++    -- * :execResult+    ExecResult(..),+    execResult,+    execResultSession,++    -- * :exec+    exec,+    execSession,++    -- * :execrows+    execRows,+    execRowsSession,++    -- * :one+    queryOne,+    queryOneSession,++    -- * :many+    queryMany,+    queryManySession,+    fold,+    foldSession,++    -- * :copyfrom+    execMany,+    execManySession,++    -- * Reexports+    Hasql.Connection.Connection,+    Hasql.Session.Session,+    RunnerError,+    Hasql.Errors.SessionError,+  ) where++import Data.Foldable (Foldable)+import Data.Vector (Vector)+import GHC.TypeLits (Symbol)+import qualified Data.Aeson+import qualified Data.ByteString+import qualified Data.Foldable+import qualified Data.Int+import qualified Data.Scientific+import qualified Data.Text+import qualified Data.Time+import qualified Data.UUID+import qualified Data.Vector+import qualified Hasql.Connection+import qualified Hasql.Decoders+import qualified Hasql.Encoders+import qualified Hasql.Errors+import qualified Hasql.Pipeline+import qualified Hasql.Session+import qualified Hasql.Statement++-- | What a runner reports when it fails.+--+-- hasql 2.1 replaced the single session-error type that+-- 'Hasql.Connection.use' returned with @UseError@, which distinguishes a+-- statement that failed on a live connection (@SessionUseError@, wrapping the+-- 'Hasql.Errors.SessionError' the old type expressed) from a connection that is+-- gone and has already been closed (@ConnectionUseError@).+--+-- Named through this alias so the generated module compiles against both, and+-- so a call site that only passes the error along does not have to care.+#if MIN_VERSION_hasql(2,1,0)+type RunnerError = Hasql.Errors.UseError+#else+type RunnerError = Hasql.Errors.SessionError+#endif++-- | The SQL of a query, with PostgreSQL's positional @$1@, @$2@ placeholders+-- left as sqlc emitted them.+newtype Query (name :: Symbol) (command :: Symbol)+  = Query Data.Text.Text++data family Params (name :: Symbol)++data family Result (name :: Symbol)++data family Enum (name :: Symbol)++-- | The parameter encoder of a query. hasql has no such class of its own --+-- encoders are plain values -- so sqlc-hs declares one and generates an+-- instance per query.+class ToRow a where+  toRow :: Hasql.Encoders.Params a++-- | The row decoder of a query's result. The counterpart of 'ToRow'.+class FromRow a where+  fromRow :: Hasql.Decoders.Row a++-- | The codec of a single value.+--+-- sqlc-hs takes hasql's codec from the SQL type for every column it typed+-- itself, because hasql checks column types when decoding and @text@, @varchar@+-- and @bpchar@ need three different decoders. Columns typed through an+-- @overrides@ entry go through this class instead, so a custom type only needs+-- an instance here:+--+-- @+-- instance ToField MyId where+--   toField = Data.Functor.Contravariant.contramap unMyId toField+-- @+--+-- An override can also name the codecs directly, with the @hasql_encoder@ and+-- @hasql_decoder@ keys, in which case no instance is needed.+class ToField a where+  toField :: Hasql.Encoders.Value a++-- | The counterpart of 'ToField'.+--+-- @+-- instance FromField MyId where+--   fromField = fmap MyId fromField+-- @+class FromField a where+  fromField :: Hasql.Decoders.Value a++instance ToField Bool where+  toField = Hasql.Encoders.bool++instance ToField Data.Int.Int16 where+  toField = Hasql.Encoders.int2++instance ToField Data.Int.Int32 where+  toField = Hasql.Encoders.int4++instance ToField Data.Int.Int64 where+  toField = Hasql.Encoders.int8++instance ToField Float where+  toField = Hasql.Encoders.float4++instance ToField Double where+  toField = Hasql.Encoders.float8++instance ToField Data.Scientific.Scientific where+  toField = Hasql.Encoders.numeric++instance ToField Char where+  toField = Hasql.Encoders.char++instance ToField Data.Text.Text where+  toField = Hasql.Encoders.text++instance ToField Data.ByteString.ByteString where+  toField = Hasql.Encoders.bytea++instance ToField Data.UUID.UUID where+  toField = Hasql.Encoders.uuid++instance ToField Data.Time.Day where+  toField = Hasql.Encoders.date++instance ToField Data.Time.LocalTime where+  toField = Hasql.Encoders.timestamp++instance ToField Data.Time.UTCTime where+  toField = Hasql.Encoders.timestamptz++instance ToField Data.Time.TimeOfDay where+  toField = Hasql.Encoders.time++instance ToField (Data.Time.TimeOfDay, Data.Time.TimeZone) where+  toField = Hasql.Encoders.timetz++instance ToField Data.Time.DiffTime where+  toField = Hasql.Encoders.interval++instance ToField Data.Aeson.Value where+  toField = Hasql.Encoders.jsonb++instance (ToField a) => ToField (Data.Vector.Vector a) where+  toField = Hasql.Encoders.foldableArray (Hasql.Encoders.nonNullable toField)++instance (ToField a) => ToField [a] where+  toField = Hasql.Encoders.foldableArray (Hasql.Encoders.nonNullable toField)++instance FromField Bool where+  fromField = Hasql.Decoders.bool++instance FromField Data.Int.Int16 where+  fromField = Hasql.Decoders.int2++instance FromField Data.Int.Int32 where+  fromField = Hasql.Decoders.int4++instance FromField Data.Int.Int64 where+  fromField = Hasql.Decoders.int8++instance FromField Float where+  fromField = Hasql.Decoders.float4++instance FromField Double where+  fromField = Hasql.Decoders.float8++instance FromField Data.Scientific.Scientific where+  fromField = Hasql.Decoders.numeric++instance FromField Char where+  fromField = Hasql.Decoders.char++instance FromField Data.Text.Text where+  fromField = Hasql.Decoders.text++instance FromField Data.ByteString.ByteString where+  fromField = Hasql.Decoders.bytea++instance FromField Data.UUID.UUID where+  fromField = Hasql.Decoders.uuid++instance FromField Data.Time.Day where+  fromField = Hasql.Decoders.date++instance FromField Data.Time.LocalTime where+  fromField = Hasql.Decoders.timestamp++instance FromField Data.Time.UTCTime where+  fromField = Hasql.Decoders.timestamptz++instance FromField Data.Time.TimeOfDay where+  fromField = Hasql.Decoders.time++instance FromField (Data.Time.TimeOfDay, Data.Time.TimeZone) where+  fromField = Hasql.Decoders.timetz++instance FromField Data.Time.DiffTime where+  fromField = Hasql.Decoders.interval++instance FromField Data.Aeson.Value where+  fromField = Hasql.Decoders.jsonb++instance (FromField a) => FromField (Data.Vector.Vector a) where+  fromField = Hasql.Decoders.vectorArray (Hasql.Decoders.nonNullable fromField)++instance (FromField a) => FromField [a] where+  fromField = Hasql.Decoders.listArray (Hasql.Decoders.nonNullable fromField)++data ExecResult = ExecResult+  { rowsAffected :: !Data.Int.Int64+  }++-- | The statement a query and a result decoder make up. Use it to run a query+-- inside a 'Hasql.Pipeline.Pipeline'.+statement ::+  (ToRow (Params name)) =>+  Query name command ->+  Hasql.Decoders.Result result ->+  Hasql.Statement.Statement (Params name) result+statement (Query sql) decoder =+  Hasql.Statement.preparable sql toRow decoder++exec ::+  (ToRow (Params name)) =>+  Hasql.Connection.Connection ->+  Query name ":exec" ->+  Params name ->+  IO (Either RunnerError ())+exec connection query params =+  Hasql.Connection.use connection (execSession query params)++execSession ::+  (ToRow (Params name)) =>+  Query name ":exec" ->+  Params name ->+  Hasql.Session.Session ()+execSession query params =+  Hasql.Session.statement params (statement query Hasql.Decoders.noResult)++execRows ::+  (ToRow (Params name)) =>+  Hasql.Connection.Connection ->+  Query name ":execrows" ->+  Params name ->+  IO (Either RunnerError Data.Int.Int64)+execRows connection query params =+  Hasql.Connection.use connection (execRowsSession query params)++execRowsSession ::+  (ToRow (Params name)) =>+  Query name ":execrows" ->+  Params name ->+  Hasql.Session.Session Data.Int.Int64+execRowsSession query params =+  Hasql.Session.statement params (statement query Hasql.Decoders.rowsAffected)++execResult ::+  (ToRow (Params name)) =>+  Hasql.Connection.Connection ->+  Query name ":execresult" ->+  Params name ->+  IO (Either RunnerError ExecResult)+execResult connection query params =+  Hasql.Connection.use connection (execResultSession query params)++execResultSession ::+  (ToRow (Params name)) =>+  Query name ":execresult" ->+  Params name ->+  Hasql.Session.Session ExecResult+execResultSession query params = do+  rowsAffected <- Hasql.Session.statement params (statement query Hasql.Decoders.rowsAffected)+  pure ExecResult {+    rowsAffected+  }++queryOne ::+  (ToRow (Params name), FromRow (Result name)) =>+  Hasql.Connection.Connection ->+  Query name ":one" ->+  Params name ->+  IO (Either RunnerError (Maybe (Result name)))+queryOne connection query params =+  Hasql.Connection.use connection (queryOneSession query params)++queryOneSession ::+  (ToRow (Params name), FromRow (Result name)) =>+  Query name ":one" ->+  Params name ->+  Hasql.Session.Session (Maybe (Result name))+queryOneSession query params =+  Hasql.Session.statement params (statement query (Hasql.Decoders.rowMaybe fromRow))++queryMany ::+  (ToRow (Params name), FromRow (Result name)) =>+  Hasql.Connection.Connection ->+  Query name ":many" ->+  Params name ->+  IO (Either RunnerError (Vector (Result name)))+queryMany connection query params =+  Hasql.Connection.use connection (queryManySession query params)++queryManySession ::+  (ToRow (Params name), FromRow (Result name)) =>+  Query name ":many" ->+  Params name ->+  Hasql.Session.Session (Vector (Result name))+queryManySession query params =+  Hasql.Session.statement params (statement query (Hasql.Decoders.rowVector fromRow))++-- | Note that hasql folds a result purely, so unlike the postgresql-simple+-- backend the step function is not in 'IO'.+fold ::+  (ToRow (Params name), FromRow (Result name)) =>+  Hasql.Connection.Connection ->+  Query name ":many" ->+  Params name ->+  a ->+  (a -> Result name -> a) ->+  IO (Either RunnerError a)+fold connection query params initial step =+  Hasql.Connection.use connection (foldSession query params initial step)+{-# INLINABLE fold #-}++foldSession ::+  (ToRow (Params name), FromRow (Result name)) =>+  Query name ":many" ->+  Params name ->+  a ->+  (a -> Result name -> a) ->+  Hasql.Session.Session a+foldSession query params initial step =+  Hasql.Session.statement params (statement query (Hasql.Decoders.foldlRows step initial fromRow))+{-# INLINABLE foldSession #-}++-- | Runs the query once per set of parameters, pipelined into a single+-- round trip, and returns the total number of rows affected.+execMany ::+  (ToRow (Params name), Foldable f) =>+  Hasql.Connection.Connection ->+  Query name ":copyfrom" ->+  f (Params name) ->+  IO (Either RunnerError Data.Int.Int64)+execMany connection query params =+  Hasql.Connection.use connection (execManySession query params)++execManySession ::+  (ToRow (Params name), Foldable f) =>+  Query name ":copyfrom" ->+  f (Params name) ->+  Hasql.Session.Session Data.Int.Int64+execManySession query params =+  Hasql.Session.pipeline+    (fmap sum (traverse pipelined (Data.Foldable.toList params)))+  where+    pipelined param =+      Hasql.Pipeline.statement param (statement query Hasql.Decoders.rowsAffected)
+ test/golden/hasql-features/Queries/Types.hs view
@@ -0,0 +1,41 @@+{- This file was auto-generated by sqlc-hs. -}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+module Queries.Types where++import qualified Hasql.Decoders+import qualified Hasql.Encoders+import Queries.Internal+import Prelude hiding (Enum)+import qualified Prelude++data instance Enum "organization_role"+  = Enum_organization_role_owner+  | Enum_organization_role_admin+  | Enum_organization_role_member+  deriving stock (Eq, Ord, Show, Bounded, Prelude.Enum)++instance ToField (Enum "organization_role") where+  toField =+    Hasql.Encoders.enum Prelude.Nothing "organization_role" $ \x ->+      case x of+        Enum_organization_role_owner -> "owner"+        Enum_organization_role_admin -> "admin"+        Enum_organization_role_member -> "member"++instance FromField (Enum "organization_role") where+  fromField =+    Hasql.Decoders.enum Prelude.Nothing "organization_role" $ \x ->+      case x of+        "owner" -> Prelude.Just Enum_organization_role_owner+        "admin" -> Prelude.Just Enum_organization_role_admin+        "member" -> Prelude.Just Enum_organization_role_member+        _ -> Prelude.Nothing+
+ test/golden/hasql-features/hasql-features.cabal view
@@ -0,0 +1,24 @@+cabal-version: 3.0+name: hasql-features+version: 0.1.0.0+library+  build-depends:+    aeson,+    base,+    bytestring,+    hasql,+    scientific,+    text,+    time,+    uuid,+    vector,+  exposed-modules:+    Queries+    Queries.DeleteUsers+    Queries.FindMembers+    Queries.FindPosts+    Queries.FindUserByName+    Queries.FindUsers+    Queries.GetEvent+    Queries.Internal+    Queries.Types
+ test/golden/simple-query-hasql.input view
@@ -0,0 +1,82 @@+settings {+  engine: "postgresql"+}++queries {+  text: "SELECT * FROM users WHERE $1 > 42;"+  name: "ListUsers"+  cmd: ":many"+  filename: "query/users.sql"+  columns {+    name: "id"+    not_null: true+    type {+      name: "int"+    }+  }+  columns {+    name: "name"+    not_null: true+    type {+      name: "pg_catalog.varchar"+    }+  }+  columns {+    name: "nickname"+    type {+      name: "text"+    }+  }+  columns {+    name: "initial"+    not_null: true+    type {+      name: "pg_catalog.bpchar"+    }+  }+  columns {+    name: "is_admin"+    not_null: true+    type {+      name: "bool"+    }+  }+  columns {+    name: "balance"+    not_null: true+    type {+      name: "numeric"+    }+  }+  columns {+    name: "ratio"+    type {+      name: "pg_catalog.float8"+    }+  }+  columns {+    name: "meta"+    not_null: true+    type {+      name: "jsonb"+    }+  }+  columns {+    name: "avatar"+    type {+      name: "bytea"+    }+  }+  params {+    number: 1+    column {+      name: "age"+      not_null: true+      type {+        name: "int"+      }+    }+  }+}++global_options: "{ \"cabal_package_name\": \"simple-query-hasql\", \"driver\": \"hasql\" }"
+ test/golden/simple-query-hasql/Queries.hs view
@@ -0,0 +1,10 @@+module Queries+  ( module Queries.Internal,+    module Queries.Types,+    module Queries+  )+where++import Queries.Internal+import Queries.Types+import Queries.ListUsers as Queries
+ test/golden/simple-query-hasql/Queries/Internal.hs view
@@ -0,0 +1,406 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE TypeFamilies #-}+module Queries.Internal (+    Query(..),+    Params,+    Result,+    Queries.Internal.Enum,++    -- * Codecs+    ToRow(..),+    FromRow(..),+    ToField(..),+    FromField(..),+    statement,++    -- * :execResult+    ExecResult(..),+    execResult,+    execResultSession,++    -- * :exec+    exec,+    execSession,++    -- * :execrows+    execRows,+    execRowsSession,++    -- * :one+    queryOne,+    queryOneSession,++    -- * :many+    queryMany,+    queryManySession,+    fold,+    foldSession,++    -- * :copyfrom+    execMany,+    execManySession,++    -- * Reexports+    Hasql.Connection.Connection,+    Hasql.Session.Session,+    RunnerError,+    Hasql.Errors.SessionError,+  ) where++import Data.Foldable (Foldable)+import Data.Vector (Vector)+import GHC.TypeLits (Symbol)+import qualified Data.Aeson+import qualified Data.ByteString+import qualified Data.Foldable+import qualified Data.Int+import qualified Data.Scientific+import qualified Data.Text+import qualified Data.Time+import qualified Data.UUID+import qualified Data.Vector+import qualified Hasql.Connection+import qualified Hasql.Decoders+import qualified Hasql.Encoders+import qualified Hasql.Errors+import qualified Hasql.Pipeline+import qualified Hasql.Session+import qualified Hasql.Statement++-- | What a runner reports when it fails.+--+-- hasql 2.1 replaced the single session-error type that+-- 'Hasql.Connection.use' returned with @UseError@, which distinguishes a+-- statement that failed on a live connection (@SessionUseError@, wrapping the+-- 'Hasql.Errors.SessionError' the old type expressed) from a connection that is+-- gone and has already been closed (@ConnectionUseError@).+--+-- Named through this alias so the generated module compiles against both, and+-- so a call site that only passes the error along does not have to care.+#if MIN_VERSION_hasql(2,1,0)+type RunnerError = Hasql.Errors.UseError+#else+type RunnerError = Hasql.Errors.SessionError+#endif++-- | The SQL of a query, with PostgreSQL's positional @$1@, @$2@ placeholders+-- left as sqlc emitted them.+newtype Query (name :: Symbol) (command :: Symbol)+  = Query Data.Text.Text++data family Params (name :: Symbol)++data family Result (name :: Symbol)++data family Enum (name :: Symbol)++-- | The parameter encoder of a query. hasql has no such class of its own --+-- encoders are plain values -- so sqlc-hs declares one and generates an+-- instance per query.+class ToRow a where+  toRow :: Hasql.Encoders.Params a++-- | The row decoder of a query's result. The counterpart of 'ToRow'.+class FromRow a where+  fromRow :: Hasql.Decoders.Row a++-- | The codec of a single value.+--+-- sqlc-hs takes hasql's codec from the SQL type for every column it typed+-- itself, because hasql checks column types when decoding and @text@, @varchar@+-- and @bpchar@ need three different decoders. Columns typed through an+-- @overrides@ entry go through this class instead, so a custom type only needs+-- an instance here:+--+-- @+-- instance ToField MyId where+--   toField = Data.Functor.Contravariant.contramap unMyId toField+-- @+--+-- An override can also name the codecs directly, with the @hasql_encoder@ and+-- @hasql_decoder@ keys, in which case no instance is needed.+class ToField a where+  toField :: Hasql.Encoders.Value a++-- | The counterpart of 'ToField'.+--+-- @+-- instance FromField MyId where+--   fromField = fmap MyId fromField+-- @+class FromField a where+  fromField :: Hasql.Decoders.Value a++instance ToField Bool where+  toField = Hasql.Encoders.bool++instance ToField Data.Int.Int16 where+  toField = Hasql.Encoders.int2++instance ToField Data.Int.Int32 where+  toField = Hasql.Encoders.int4++instance ToField Data.Int.Int64 where+  toField = Hasql.Encoders.int8++instance ToField Float where+  toField = Hasql.Encoders.float4++instance ToField Double where+  toField = Hasql.Encoders.float8++instance ToField Data.Scientific.Scientific where+  toField = Hasql.Encoders.numeric++instance ToField Char where+  toField = Hasql.Encoders.char++instance ToField Data.Text.Text where+  toField = Hasql.Encoders.text++instance ToField Data.ByteString.ByteString where+  toField = Hasql.Encoders.bytea++instance ToField Data.UUID.UUID where+  toField = Hasql.Encoders.uuid++instance ToField Data.Time.Day where+  toField = Hasql.Encoders.date++instance ToField Data.Time.LocalTime where+  toField = Hasql.Encoders.timestamp++instance ToField Data.Time.UTCTime where+  toField = Hasql.Encoders.timestamptz++instance ToField Data.Time.TimeOfDay where+  toField = Hasql.Encoders.time++instance ToField (Data.Time.TimeOfDay, Data.Time.TimeZone) where+  toField = Hasql.Encoders.timetz++instance ToField Data.Time.DiffTime where+  toField = Hasql.Encoders.interval++instance ToField Data.Aeson.Value where+  toField = Hasql.Encoders.jsonb++instance (ToField a) => ToField (Data.Vector.Vector a) where+  toField = Hasql.Encoders.foldableArray (Hasql.Encoders.nonNullable toField)++instance (ToField a) => ToField [a] where+  toField = Hasql.Encoders.foldableArray (Hasql.Encoders.nonNullable toField)++instance FromField Bool where+  fromField = Hasql.Decoders.bool++instance FromField Data.Int.Int16 where+  fromField = Hasql.Decoders.int2++instance FromField Data.Int.Int32 where+  fromField = Hasql.Decoders.int4++instance FromField Data.Int.Int64 where+  fromField = Hasql.Decoders.int8++instance FromField Float where+  fromField = Hasql.Decoders.float4++instance FromField Double where+  fromField = Hasql.Decoders.float8++instance FromField Data.Scientific.Scientific where+  fromField = Hasql.Decoders.numeric++instance FromField Char where+  fromField = Hasql.Decoders.char++instance FromField Data.Text.Text where+  fromField = Hasql.Decoders.text++instance FromField Data.ByteString.ByteString where+  fromField = Hasql.Decoders.bytea++instance FromField Data.UUID.UUID where+  fromField = Hasql.Decoders.uuid++instance FromField Data.Time.Day where+  fromField = Hasql.Decoders.date++instance FromField Data.Time.LocalTime where+  fromField = Hasql.Decoders.timestamp++instance FromField Data.Time.UTCTime where+  fromField = Hasql.Decoders.timestamptz++instance FromField Data.Time.TimeOfDay where+  fromField = Hasql.Decoders.time++instance FromField (Data.Time.TimeOfDay, Data.Time.TimeZone) where+  fromField = Hasql.Decoders.timetz++instance FromField Data.Time.DiffTime where+  fromField = Hasql.Decoders.interval++instance FromField Data.Aeson.Value where+  fromField = Hasql.Decoders.jsonb++instance (FromField a) => FromField (Data.Vector.Vector a) where+  fromField = Hasql.Decoders.vectorArray (Hasql.Decoders.nonNullable fromField)++instance (FromField a) => FromField [a] where+  fromField = Hasql.Decoders.listArray (Hasql.Decoders.nonNullable fromField)++data ExecResult = ExecResult+  { rowsAffected :: !Data.Int.Int64+  }++-- | The statement a query and a result decoder make up. Use it to run a query+-- inside a 'Hasql.Pipeline.Pipeline'.+statement ::+  (ToRow (Params name)) =>+  Query name command ->+  Hasql.Decoders.Result result ->+  Hasql.Statement.Statement (Params name) result+statement (Query sql) decoder =+  Hasql.Statement.preparable sql toRow decoder++exec ::+  (ToRow (Params name)) =>+  Hasql.Connection.Connection ->+  Query name ":exec" ->+  Params name ->+  IO (Either RunnerError ())+exec connection query params =+  Hasql.Connection.use connection (execSession query params)++execSession ::+  (ToRow (Params name)) =>+  Query name ":exec" ->+  Params name ->+  Hasql.Session.Session ()+execSession query params =+  Hasql.Session.statement params (statement query Hasql.Decoders.noResult)++execRows ::+  (ToRow (Params name)) =>+  Hasql.Connection.Connection ->+  Query name ":execrows" ->+  Params name ->+  IO (Either RunnerError Data.Int.Int64)+execRows connection query params =+  Hasql.Connection.use connection (execRowsSession query params)++execRowsSession ::+  (ToRow (Params name)) =>+  Query name ":execrows" ->+  Params name ->+  Hasql.Session.Session Data.Int.Int64+execRowsSession query params =+  Hasql.Session.statement params (statement query Hasql.Decoders.rowsAffected)++execResult ::+  (ToRow (Params name)) =>+  Hasql.Connection.Connection ->+  Query name ":execresult" ->+  Params name ->+  IO (Either RunnerError ExecResult)+execResult connection query params =+  Hasql.Connection.use connection (execResultSession query params)++execResultSession ::+  (ToRow (Params name)) =>+  Query name ":execresult" ->+  Params name ->+  Hasql.Session.Session ExecResult+execResultSession query params = do+  rowsAffected <- Hasql.Session.statement params (statement query Hasql.Decoders.rowsAffected)+  pure ExecResult {+    rowsAffected+  }++queryOne ::+  (ToRow (Params name), FromRow (Result name)) =>+  Hasql.Connection.Connection ->+  Query name ":one" ->+  Params name ->+  IO (Either RunnerError (Maybe (Result name)))+queryOne connection query params =+  Hasql.Connection.use connection (queryOneSession query params)++queryOneSession ::+  (ToRow (Params name), FromRow (Result name)) =>+  Query name ":one" ->+  Params name ->+  Hasql.Session.Session (Maybe (Result name))+queryOneSession query params =+  Hasql.Session.statement params (statement query (Hasql.Decoders.rowMaybe fromRow))++queryMany ::+  (ToRow (Params name), FromRow (Result name)) =>+  Hasql.Connection.Connection ->+  Query name ":many" ->+  Params name ->+  IO (Either RunnerError (Vector (Result name)))+queryMany connection query params =+  Hasql.Connection.use connection (queryManySession query params)++queryManySession ::+  (ToRow (Params name), FromRow (Result name)) =>+  Query name ":many" ->+  Params name ->+  Hasql.Session.Session (Vector (Result name))+queryManySession query params =+  Hasql.Session.statement params (statement query (Hasql.Decoders.rowVector fromRow))++-- | Note that hasql folds a result purely, so unlike the postgresql-simple+-- backend the step function is not in 'IO'.+fold ::+  (ToRow (Params name), FromRow (Result name)) =>+  Hasql.Connection.Connection ->+  Query name ":many" ->+  Params name ->+  a ->+  (a -> Result name -> a) ->+  IO (Either RunnerError a)+fold connection query params initial step =+  Hasql.Connection.use connection (foldSession query params initial step)+{-# INLINABLE fold #-}++foldSession ::+  (ToRow (Params name), FromRow (Result name)) =>+  Query name ":many" ->+  Params name ->+  a ->+  (a -> Result name -> a) ->+  Hasql.Session.Session a+foldSession query params initial step =+  Hasql.Session.statement params (statement query (Hasql.Decoders.foldlRows step initial fromRow))+{-# INLINABLE foldSession #-}++-- | Runs the query once per set of parameters, pipelined into a single+-- round trip, and returns the total number of rows affected.+execMany ::+  (ToRow (Params name), Foldable f) =>+  Hasql.Connection.Connection ->+  Query name ":copyfrom" ->+  f (Params name) ->+  IO (Either RunnerError Data.Int.Int64)+execMany connection query params =+  Hasql.Connection.use connection (execManySession query params)++execManySession ::+  (ToRow (Params name), Foldable f) =>+  Query name ":copyfrom" ->+  f (Params name) ->+  Hasql.Session.Session Data.Int.Int64+execManySession query params =+  Hasql.Session.pipeline+    (fmap sum (traverse pipelined (Data.Foldable.toList params)))+  where+    pipelined param =+      Hasql.Pipeline.statement param (statement query Hasql.Decoders.rowsAffected)
+ test/golden/simple-query-hasql/Queries/ListUsers.hs view
@@ -0,0 +1,68 @@+{- This file was auto-generated from query/users.sql by sqlc-hs. -}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+module Queries.ListUsers where++import Queries.Internal (Query(..), Enum, Params, Result, ToRow(..), FromRow(..), ToField(..), FromField(..))+import qualified Hasql.Decoders+import qualified Hasql.Encoders++import qualified Data.Int+import qualified Data.Text+import qualified GHC.Base+import qualified GHC.Types+import qualified Data.Scientific+import qualified Data.Aeson+import qualified Data.ByteString+import qualified Data.Foldable+import qualified Data.Functor.Contravariant++query_ListUsers :: Query "ListUsers" ":many"+query_ListUsers = Query "SELECT * FROM users WHERE $1 > 42;"++data instance Params "ListUsers" = Params_ListUsers+  {+    age :: Data.Int.Int32+  }++data instance Result "ListUsers" = Result_ListUsers+  {+    id :: !(Data.Int.Int32),+    name :: !(Data.Text.Text),+    nickname :: !(GHC.Base.Maybe Data.Text.Text),+    initial :: !(Data.Text.Text),+    is_admin :: !(GHC.Types.Bool),+    balance :: !(Data.Scientific.Scientific),+    ratio :: !(GHC.Base.Maybe GHC.Types.Double),+    meta :: !(Data.Aeson.Value),+    avatar :: !(GHC.Base.Maybe Data.ByteString.ByteString)+  }++instance ToRow (Params "ListUsers") where+  {-# INLINE toRow #-}+  toRow =+    mconcat+      [ +      Data.Functor.Contravariant.contramap (\Params_ListUsers{..} -> age) (Hasql.Encoders.param (Hasql.Encoders.nonNullable Hasql.Encoders.int4))+      ]++instance FromRow (Result "ListUsers") where+  {-# INLINE fromRow #-}+  fromRow =+    pure Result_ListUsers+      <*> Hasql.Decoders.column (Hasql.Decoders.nonNullable Hasql.Decoders.int4)+      <*> Hasql.Decoders.column (Hasql.Decoders.nonNullable Hasql.Decoders.varchar)+      <*> Hasql.Decoders.column (Hasql.Decoders.nullable Hasql.Decoders.text)+      <*> Hasql.Decoders.column (Hasql.Decoders.nonNullable Hasql.Decoders.bpchar)+      <*> Hasql.Decoders.column (Hasql.Decoders.nonNullable Hasql.Decoders.bool)+      <*> Hasql.Decoders.column (Hasql.Decoders.nonNullable Hasql.Decoders.numeric)+      <*> Hasql.Decoders.column (Hasql.Decoders.nullable Hasql.Decoders.float8)+      <*> Hasql.Decoders.column (Hasql.Decoders.nonNullable Hasql.Decoders.jsonb)+      <*> Hasql.Decoders.column (Hasql.Decoders.nullable Hasql.Decoders.bytea)++
+ test/golden/simple-query-hasql/Queries/Types.hs view
@@ -0,0 +1,18 @@+{- This file was auto-generated by sqlc-hs. -}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+module Queries.Types where++import qualified Hasql.Decoders+import qualified Hasql.Encoders+import Queries.Internal+import Prelude hiding (Enum)+import qualified Prelude+
+ test/golden/simple-query-hasql/simple-query-hasql.cabal view
@@ -0,0 +1,20 @@+cabal-version: 3.0+name: simple-query-hasql+version: 0.1.0.0+library+  build-depends:+    aeson,+    base,+    bytestring,+    ghc-prim,+    hasql,+    scientific,+    text,+    time,+    uuid,+    vector,+  exposed-modules:+    Queries+    Queries.Internal+    Queries.ListUsers+    Queries.Types
+ test/golden/sqlite-arg-placeholders.input view
@@ -0,0 +1,46 @@+settings {+  engine: "sqlite"+}++queries {+  text: "SELECT * FROM users WHERE name = ?1 OR nickname = ?1 OR age > ?2;"+  name: "FindUsers"+  cmd: "SELECT"+  filename: "query/users.sql"+  columns {+    name: "id"+    not_null: true+    type {+      name: "int"+    }+  }+  columns {+    name: "name"+    not_null: true+    type {+      name: "text"+    }+  }+  params {+    number: 1+    column {+      name: "name"+      not_null: true+      type {+        name: "text"+      }+    }+  }+  params {+    number: 2+    column {+      name: "min_age"+      not_null: true+      type {+        name: "int"+      }+    }+  }+}++global_options: "{ \"cabal_package_name\": \"sqlite-arg-placeholders\", \"cabal_default_extensions\": [\"OverloadedStrings\", \"StrictData\"] }"
+ test/golden/sqlite-arg-placeholders/Queries.hs view
@@ -0,0 +1,10 @@+module Queries+  ( module Queries.Internal,+    module Queries.Types,+    module Queries+  )+where++import Queries.Internal+import Queries.Types+import Queries.FindUsers as Queries
+ test/golden/sqlite-arg-placeholders/Queries/FindUsers.hs view
@@ -0,0 +1,51 @@+{- This file was auto-generated from query/users.sql by sqlc-hs. -}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+module Queries.FindUsers where++import Queries.Internal (Query(..), Enum, Params, Result)+import qualified Database.SQLite.Simple.FromRow+import qualified Database.SQLite.Simple.ToField+import qualified Database.SQLite.Simple.ToRow++import qualified Data.Text+import qualified Data.Int+import qualified Data.Foldable++query_FindUsers :: Query "FindUsers" "SELECT"+query_FindUsers = Query "SELECT * FROM users WHERE name = ? OR nickname = ? OR age > ?;"++data instance Params "FindUsers" = Params_FindUsers+  {+    name :: Data.Text.Text,+    min_age :: Data.Int.Int64+  }++data instance Result "FindUsers" = Result_FindUsers+  {+    id :: !(Data.Int.Int64),+    name :: !(Data.Text.Text)+  }+++instance Database.SQLite.Simple.ToRow.ToRow (Params "FindUsers") where+  toRow Params_FindUsers{..} =+    [ +      Database.SQLite.Simple.ToField.toField name, ++      Database.SQLite.Simple.ToField.toField name, ++      Database.SQLite.Simple.ToField.toField min_age+    ]++instance Database.SQLite.Simple.FromRow.FromRow (Result "FindUsers") where+  fromRow =+    pure Result_FindUsers+      <*> Database.SQLite.Simple.FromRow.field+      <*> Database.SQLite.Simple.FromRow.field+
+ test/golden/sqlite-arg-placeholders/Queries/Internal.hs view
@@ -0,0 +1,151 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE TypeFamilies #-}+module Queries.Internal (+    Query(..),+    Params,+    Result,+    Queries.Internal.Enum,++    -- * :execResult+    ExecResult(..),+    execResult,++    -- * :exec+    exec,++    -- * :execrows+    execRows,++    -- * :execlastid+    execLastId,++    -- * :one+    queryOne,++    -- * :many+    queryMany,+    fold,++    -- * Reexports+    Database.SQLite.Simple.Connection,+    Database.SQLite.Simple.ToRow,+    Database.SQLite.Simple.FromRow,+  ) where++import Database.SQLite.Simple (Connection, FromRow, ToRow)+import GHC.TypeLits (Symbol)+import qualified Data.Int+import qualified Database.SQLite.Simple+import qualified Data.Vector+import qualified Data.Vector.Mutable++newtype Query (name :: Symbol) (command :: Symbol)+  = Query Database.SQLite.Simple.Query++data family Params (name :: Symbol)++data family Result (name :: Symbol)++data family Enum (name :: Symbol)++data ExecResult = ExecResult+  { lastInsertId :: !Data.Int.Int64,+    rowsAffected :: !Data.Int.Int64+  }++exec ::+  (ToRow (Params name)) =>+  Connection ->+  Query name ":exec" ->+  Params name ->+  IO ()+exec connection (Query sql) =+  Database.SQLite.Simple.execute connection sql++execRows ::+  (ToRow (Params name)) =>+  Connection ->+  Query name ":execrows" ->+  Params name ->+  IO Data.Int.Int64+execRows connection (Query sql) params = do+  Database.SQLite.Simple.execute connection sql params+  rowsAffected <- Database.SQLite.Simple.changes connection+  pure (fromIntegral rowsAffected)++execLastId ::+  (ToRow (Params name)) =>+  Connection ->+  Query name ":execlastid" ->+  Params name ->+  IO Data.Int.Int64+execLastId connection (Query sql) params = do+  Database.SQLite.Simple.execute connection sql params+  lastInsertId <- Database.SQLite.Simple.lastInsertRowId connection+  pure lastInsertId++execResult ::+  (ToRow (Params name)) =>+  Connection ->+  Query name ":execresult" ->+  Params name ->+  IO ExecResult+execResult connection (Query sql) params = do+  Database.SQLite.Simple.execute connection sql params+  rowsAffected <- Database.SQLite.Simple.changes connection+  lastInsertId <- Database.SQLite.Simple.lastInsertRowId connection+  pure ExecResult {+    lastInsertId,+    rowsAffected = fromIntegral rowsAffected+  }++queryOne ::+  (ToRow (Params name), FromRow (Result name)) =>+  Connection ->+  Query name ":one" ->+  Params name ->+  IO (Maybe (Result name))+queryOne connection (Query sql) params = do+  result <- Database.SQLite.Simple.query connection sql params+  case result of+    [] -> pure Nothing+    x : _ -> pure (Just x)++data Grow v = Grow !Int !v++queryMany ::+  (ToRow (Params name), FromRow (Result name)) =>+  Connection ->+  Query name ":many" ->+  Params name ->+  IO (Data.Vector.Vector (Result name))+queryMany connection query params = do+  vector <- Data.Vector.Mutable.unsafeNew 4+  Grow i vector <- fold connection query params (Grow 0 vector) step+  vector <- Data.Vector.unsafeFreeze vector+  pure $! Data.Vector.unsafeTake i vector+  where+    step (Grow i vector) !result+      | i < Data.Vector.Mutable.length vector = do+          Data.Vector.Mutable.unsafeWrite vector i result+          pure $! Grow (i + 1) vector+      | otherwise = do+          -- Grow vector exponentially by doubling its size+          vector <- Data.Vector.Mutable.unsafeGrow vector i+          Data.Vector.Mutable.unsafeWrite vector i result+          pure $! Grow (i + 1) vector++fold ::+  (ToRow (Params name), FromRow (Result name)) =>+  Connection ->+  Query name ":many" ->+  Params name ->+  a ->+  (a -> Result name -> IO a) ->+  IO a+fold connection (Query sql) =+  Database.SQLite.Simple.fold connection sql+{-# INLINABLE fold #-}
+ test/golden/sqlite-arg-placeholders/Queries/Types.hs view
@@ -0,0 +1,19 @@+{- This file was auto-generated by sqlc-hs. -}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+module Queries.Types where++import qualified Database.SQLite.Simple.FromRow+import qualified Database.SQLite.Simple.ToField+import qualified Database.SQLite.Simple.ToRow+import Queries.Internal+import Prelude hiding (Enum)+import qualified Prelude+
+ test/golden/sqlite-arg-placeholders/sqlite-arg-placeholders.cabal view
@@ -0,0 +1,18 @@+cabal-version: 3.0+name: sqlite-arg-placeholders+version: 0.1.0.0+library+  build-depends:+    base,+    bytestring,+    sqlite-simple,+    text,+    vector,+  exposed-modules:+    Queries+    Queries.FindUsers+    Queries.Internal+    Queries.Types+  default-extensions:+    OverloadedStrings+    StrictData