{"id":17997312,"url":"https://github.com/thma/generic-persistence","last_synced_at":"2025-03-26T04:31:16.238Z","repository":{"id":65291197,"uuid":"584529808","full_name":"thma/generic-persistence","owner":"thma","description":"GenericPersistence is a Haskell persistence library for relational databases. The approach relies on GHC.Generics. A real-world demo can be found here:","archived":false,"fork":false,"pushed_at":"2024-12-22T15:24:44.000Z","size":523,"stargazers_count":11,"open_issues_count":4,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-21T05:32:45.568Z","etag":null,"topics":["database","generics","haskell","orm-library","persistence","reflection"],"latest_commit_sha":null,"homepage":"https://github.com/thma/servant-gp","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thma.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-02T20:41:09.000Z","updated_at":"2025-01-11T03:38:40.000Z","dependencies_parsed_at":"2023-09-23T06:46:57.167Z","dependency_job_id":"bf37f08c-a55c-4e4a-ab99-cc7fa9fb2ebc","html_url":"https://github.com/thma/generic-persistence","commit_stats":{"total_commits":129,"total_committers":4,"mean_commits":32.25,"dds":0.1472868217054264,"last_synced_commit":"5c7805a5a71d7d75ced7a674cc0ca987cf258530"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thma%2Fgeneric-persistence","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thma%2Fgeneric-persistence/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thma%2Fgeneric-persistence/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thma%2Fgeneric-persistence/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thma","download_url":"https://codeload.github.com/thma/generic-persistence/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245589254,"owners_count":20640251,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","generics","haskell","orm-library","persistence","reflection"],"created_at":"2024-10-29T21:17:40.945Z","updated_at":"2025-03-26T04:31:16.211Z","avatar_url":"https://github.com/thma.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GenericPersistence - A Haskell Persistence Layer using Generics\n\n[![License BSD3](https://img.shields.io/badge/license-BSD3-brightgreen.svg)](http://opensource.org/licenses/BSD-3-Clause)\n[![Actions Status](https://github.com/thma/generic-persistence/workflows/Haskell%20CI/badge.svg)](https://github.com/thma/generic-persistence/actions)\n[![codecov](https://codecov.io/gh/thma/generic-persistence/graph/badge.svg?token=DBCFLEA8JZ)](https://codecov.io/gh/thma/generic-persistence)\n[![Available on Hackage](https://img.shields.io/hackage/v/generic-persistence.svg?style=flat)](https://hackage.haskell.org/package/generic-persistence)\n\n![GP Logo](https://github.com/thma/generic-persistence/blob/main/gp-logo-300.png?raw=true)\n\n\n\n## Table of Contents\n\n- [Introduction](#introduction)\n- [Status](#status)\n- [Available on Hackage](#available-on-hackage)\n- [Short demo](#short-demo)\n- [Real world examples](#real-world-examples)\n- [Deal with runtime exceptions or use total functions? Your choice!](#deal-with-runtime-exceptions-or-use-total-functions-your-choice)\n  - [Exceptions in the default API](#exceptions-in-the-default-api)\n  - [Total functions in the safe API](#total-functions-in-the-safe-api)\n- [How it works](#how-it-works)\n  - [Default Behaviour](#default-behaviour)\n  - [Customizing The Default Behaviour](#customizing-the-default-behaviour)\n- [Handling enumeration fields](#handling-enumeration-fields)\n- [Handling embedded Objects](#handling-embedded-objects)\n- [Handling 1:1 references](#handling-11-references)\n- [Handling 1:N references](#handling-1n-references)\n- [Performing queries with the Query DSL](#performing-queries-with-the-query-dsl)\n- [Integrating user defined queries](#integrating-user-defined-queries)\n- [The Conn ConnectionContext Type](#the-conn-connection-type)\n- [Connection Pooling](#connection-pooling)\n\n## Introduction\n\nGenericPersistence is a small Haskell persistence layer for relational databases. \nThe approach relies on [GHC.Generics](https://hackage.haskell.org/package/base-4.17.0.0/docs/GHC-Generics.html). The actual database access is provided by the [HDBC](https://hackage.haskell.org/package/HDBC) library.\n\nThe *functional goal* of the persistence layer is to provide hassle-free RDBMS persistence for Haskell data types in \nRecord notation (for simplicity I call these *Entities*).\n\nIt therefore provides means for inserting, updating, deleting and querying such entities into/from relational databases.\n\nThe main *design goal* is to minimize the *boilerplate* code required:\n\n- no manual instantiation of type classes\n- no implementation of encoders/decoders\n- no special naming convention for types and their attributes \n- no special types to define entities and attributes\n- no Template Haskell scaffolding of glue code\n\nIn an ideal world we would be able to take any POHO (Plain old Haskell Object) \nand persist it to any RDBMS without any additional effort.\n\n## Status\n\nAs of now there is full support for SQLite and PostgreSQL. \nSupport for other databases will be implemented on demand.\n\n### new features in v0.6\n- Autoincrement flag for primary keys can now defined per Entity\n- insert now always returns the inserted entity (thus insertReturning was removed)\n- insertMany now also respects handling of primary keys\n\n### new features in v0.5\n\n- support for PostgreSQL\n- support RETURNING statement for insert\n- support for auto-incrementing primary keys\n- entitiesFromRows now available in GP api also\n- provide a simple quasi-qoter for defining sql queries\n- expose some HDBC functions in the GP API\n- explicit setting of transaction mode\n\n\nFeature requests, feedback and pull requests are welcome!\n\n## Available on Hackage\n\n[https://hackage.haskell.org/package/generic-persistence](https://hackage.haskell.org/package/generic-persistence)\n\nAdd the following to your `package.yaml` file:\n\n```yaml\ndependencies:\n- generic-persistence\n```\n\nI would also recommend to add the setting `language: GHC2021`  to your `package.yaml` file:\n\n```yaml\nlanguage: GHC2021\n```\n\nThis drastically reduces the amount of LANGUAGE extensions that need to be added to your source files.\n\n\n## Short demo\n\nHere now follows a short demo that shows how the library looks and feels from the user's point of view.\n\n```haskell\n{-# LANGUAGE DeriveAnyClass #-} -- allows automatic derivation from Entity type class\n\nmodule Main (main) where\n\nimport           Database.GP          \nimport           Database.HDBC.Sqlite3 (connectSqlite3)\nimport           GHC.Generics\n\n-- | An Entity data type with several fields, using record syntax.\ndata Person = Person\n  { personID :: Int,\n    name     :: String,\n    age      :: Int,\n    address  :: String\n  }\n  deriving (Generic, Entity, Show) -- deriving Entity allows us to use the GenericPersistence API\n\n\nmain :: IO ()\nmain = do\n  -- connect to a database in auto commit mode\n  conn \u003c- connect AutoCommit \u003c$\u003e connectSqlite3 \"sqlite.db\"\n\n  -- initialize Person table\n  setupTableFor @Person SQLite conn\n\n  alice \u003c- insert conn Person {name = \"Alice\", age = 25, address = \"Elmstreet 1\"}\n  print alice\n\n  -- update a Person\n  update conn alice {address = \"Main Street 200\"}\n\n  -- select a Person by id\n  -- The result type must be provided by the call site,\n  -- as `selectById` has a polymorphic return type `IO (Maybe a)`.\n  alice' \u003c- selectById @Person conn (personID alice)\n  print alice'\n\n  -- select all Persons from a database. again, the result type must be provided.\n  allPersons \u003c- select @Person conn allEntries\n  print allPersons\n\n  -- select all Persons from a database, where age is smaller 30.\n  allPersonsUnder30 \u003c- select @Person conn (field \"age\" \u003c. (30 :: Int))\n  print allPersonsUnder30\n\n  -- delete a Person from a database\n  delete conn alice\n\n  -- select all Persons from a database. Now it should be empty.\n  allPersons' \u003c- select @Person conn allEntries\n  print allPersons'\n\n  -- close connection\n  disconnect conn\n```\n\n## Real world examples\n\nTo learn how to use the library in more complex scenarios, I recommend looking at the following examples:\n\n### Building a REST service with Servant and GenericPersistence\n\n[This example](https://github.com/thma/servant-gp) shows how to use servant to build a REST API that provides CRUD operations for a medium-complex data model.\nGenericPersistence is used to execute the CRUD operation against a SQLite database.\nA Swagger UI is provided to interact with the API.\n\n### Building a REST service with Scotty and GenericPersistence\n[This blog post](https://thma.github.io/posts/2024-12-05-real-worlds-rest-services-with-scotty-and-gp.html)\nexplains how to use Scotty to build a REST API that provides CRUD operations for a simple data model.\nGenericPersistence is used to execute the CRUD operation against a SQLite database.\nThis example also demonstrate how easy a paging mechanism can be implemented with GenericPersistence.\nThe code also shows how to use GenericPersistence to manage BearerTokens for validating incoming requests.\n\n### The Elephantine library review\n\nThe Elephantine library review provides a good overview of the different libraries available for working with PostgreSQL in Haskell. It evaluates the libraries based on a real world application scenario. This allows to compare the libraries based on the same use cases and see how the libraries differ in their approach.\nIt also contains a section on *Generic-Persistence*: \n[How to use PostgreSQL with Haskell. Elephantine Library Review 2023](https://github.com/Zelenya/elephants#generic-persistence)\n\nThe source code for *the Generic-Persistence based solution* can be found [here](https://github.com/Zelenya/elephants/blob/main/src/Elephants/GenericPersistence.hs).\n\n\n## Deal with runtime exceptions or use total functions? Your choice!\n\nGenericPersistence provides two different APIs for accessing the database:\n- the default API (as shown in the above demo), which uses exceptions to signal errors\n- the safe API, which uses `Either` to signal errors\n\n### Exceptions in the default API\nThe default API is the easiest to use, but you will have to do exception handling to catch runtime errors. To use it you'll have to import the `Database.GP` module:\n\n```haskell\nimport Database.GP \n```\n\nThese are the exceptions that can be thrown:\n\n```haskell\ndata PersistenceException =\n    EntityNotFound String\n  | DuplicateInsert String\n  | DatabaseError String\n  | NoUniqueKey String\n  deriving (Show, Eq, Exception)\n```\n\nThe `EntityNotFound` exception is thrown when you try to select an entity by its primary key, but no entity with the given primary key exists in the database.\n\nThe `DuplicateInsert` exception is thrown when you try to insert an entity into the database, but an entity with the same primary key already exists in the database.\n\nThe `DatabaseError` exception is thrown when the database backend returns an error.\n\nThe `NoUniqueKey` exception is thrown when you try to select an entity by its primary key, but multiple rows are returned by the database. This can happen if there is no primary key constraint defined on the underlying database column.\n\nA real world example can be found in the [Servant GP - UserServer](https://github.com/thma/servant-gp/blob/main/src/UserServer.hs) module.\n\n### Total functions in the safe API\n\nThe safe API is a bit more verbose, but it does not throw exceptions. To use it you'll have to import the `Database.GP.GenericPersistenceSafe` module:\n\n```haskell\nimport Database.GP.GenericPersistenceSafe\n```\n\nThis module provides the same function as `Database.GP`, but all functions return `Either PersistenceException a` instead of `IO a` or `IO (Maybe a)`.\n\n```haskell\neitherExRes \u003c- selectById conn \"1\" :: IO (Either PersistenceException Person)\ncase eitherExRes of\n  Left (EntityNotFound _) -\u003e print \"Entity not found\"\n  Right person            -\u003e print person\n```\n\nThis may look a bit verbose, but in actual code this may work out better, as `Either` allows pattern matching and chaining of computations with the `do` notation.\n\nA real world example can be found in the [Servant GP - UserServerSafe](https://github.com/thma/servant-gp/blob/main/src/UserServerSafe.hs) module. The `UserServerSafe` module is a copy of the `UserServer` module, but it uses the safe API instead of the default API. As you can see, the code of `UserServerSafe` is actually a bit more compact than the code of UserServer. (In the default API, we have to deal with the special case of `selectById` returning `Nothing`.)\n\n## How it works\n\nIn order to store Haskell data types in a relational database, we need to define a mapping between Haskell types and database tables.\nThis mapping is defined by the `Entity` type class. This type class comes with default implementations for all methods which define \nthe standard behaviour. (The default implementations internally use `GHC.Generics`.)\n\nThis default mapping will work for many cases, but it can be customized by overriding the default implementations.\n\n### The Entity type class\n\nThe `Entity` type class specifies the following methods:\n\n```haskell\nclass (Generic a, HasConstructor (Rep a), HasSelectors (Rep a)) =\u003e Entity a where\n  -- | Converts a database row to a value of type 'a'.\n  fromRow :: Conn -\u003e [SqlValue] -\u003e IO a\n\n  -- | Converts a value of type 'a' to a database row.\n  toRow :: Conn -\u003e a -\u003e IO [SqlValue]\n\n  -- | Returns the name of the primary key field for a type 'a'.\n  idField :: String\n\n  -- | Returns a list of tuples that map field names to column names for a type 'a'.\n  fieldsToColumns :: [(String, String)]\n\n  -- | Returns the name of the table for a type 'a'.\n  tableName :: String\n\n  -- | Returns True if the primary key field for a type 'a' is autoincremented by the database.\n  autoIncrement :: Bool\n```\n\n### Default Behaviour\n\n`idField`, `fieldsToColumns` and `tableName` are used to define the mapping between Haskell types and database tables.\n\n- The default implementations of `idField` returns a default value for the field name of the primary key field of a type `a`:\nThe type name in lower case, plus \"ID\".\nE.g. `idField @Book` will return `\"bookID\"`.\n\n- `tableName` returns the name of the database table used for type `a`. The default implementation simply returns the constructor name of `a`. E.g. `tableName @Book` will return `\"Book\"`.\n\n- `fieldsToColumns` returns a list of tuples that map field names of type `a` to database column names for a type. The default implementation simply returns a list of tuples that map the field names of `a` to the field names of `a`. E.g. `fieldsToColumns @Person` will return `[(\"personID\",\"personID\"),(\"name\",\"name\"),(\"age\",\"age\"),(\"address\",\"address\")]`.\n\n- `autoIncrement` returns `True` by default. This means that the primary key field of a type `a` is assumed to be autoincremented by the database. If this is not the case, you can override the default implementation to return `False`.\n\n`fromRow` and `toRow` are used to convert between Haskell types and database rows. \n\n- `fromRow` converts a database row, represented by a `[SqlValue]` to a value of type `a`. \n\n- `toRow` converts a value of type `a` to a `[SqlValue]`, representing a database row. \n\nThe default implementations of `fromRow` and `toRow` expects that type `a` has a single constructor and a selector for each field. All fields are expected to have a 1:1 mapping to a column in the database table.\nThus each field must have a type that can be converted to and from a `SqlValue`. \n\nFor example \n\n```haskell\ntoRow conn (Person {personID = 1234, name = \"Alice\", age = 27, address = \"Elmstreet 1\"}) \n````\n\nwill return \n\n```haskell\n[SqlInt64 1234,SqlString \"Alice\",SqlInt64 27,SqlString \"Elmstreet 1\"]\n```\n\nAnd `fromRow` does the inverse: \n```haskell\nfromRow conn [SqlInt64 1234,SqlString \"Alice\",SqlInt64 27,SqlString \"Elmstreet 1\"] :: IO Person\n``` \n\nreturns \n\n```haskell\nPerson {personID = 1234, name = \"Alice\", age = 27, address = \"Elmstreet 1\"}\n```\n\nThe conversion functions `toRow` and `fromRow` both carry an additional `Conn` argument. This argument is not used by the default implementations, but it can be used to provide database access during the conversion process. We will cover this later.\n\n### Customizing the default behaviour\n\nThe default implementations of `idField`, `fieldsToColumns`, `tableName`, `fromRow` and `toRow` can be customized by overriding the default implementations.\nOveriding `idField`, `fieldsToColumns` and `tableName` will be required when your database tables do not follow the default naming conventions.\n\nFor example, if we have a database table `BOOK_TBL` with the following columns:\n\n```sql\nCREATE TABLE BOOK_TBL \n  ( bookId INTEGER PRIMARY KEY, \n    bookTitle TEXT, \n    bookAuthor TEXT, \n    bookYear INTEGER\n  );\n```\nand we want to map this table to a Haskell data type `Book`:\n\n```haskell\ndata Book = Book\n  { book_id :: Int,\n    title   :: String,\n    author  :: String,\n    year    :: Int\n  }\n  deriving (Generic, Show)\n```\n\nThen we can customize the default implementations of `idField`, `fieldsToColumns` and `tableName` to achieve the desired mapping:\n\n```haskell\ninstance Entity Book where\n  -- this is the primary key field of the Book data type (not following the default naming convention)\n  idField = \"book_id\"\n\n  -- this defines the mapping between the field names of the Book data type and the column names of the database table\n  fieldsToColumns = [(\"book_id\", \"bookId\"), (\"title\", \"bookTitle\"), (\"author\", \"bookAuthor\"), (\"year\", \"bookYear\")]\n\n  -- this is the name of the database table\n  tableName = \"BOOK_TBL\"\n```\n\nOverriding `fromRow` and `toRow` will be required when your database tables do not follow the default mapping conventions.\nWe will see some examples in later sections.\n\n## Handling enumeration fields\n\nSay we have a data type `Book` with an enumeration field of type `BookCategory`:\n\n```haskell\ndata Book = Book\n  { bookID :: Int,\n    title   :: String,\n    author  :: String,\n    year    :: Int,\n    category :: BookCategory\n  }\n  deriving (Generic, Entity, Show)\n\ndata BookCategory = Fiction | Travel | Arts | Science | History | Biography | Other\n  deriving (Generic, Show, Enum)\n```\n\nIn this case everything works out of the box, because *GenericPersistence* provides `Convertible` instances for all `Enum` types. `Convertible` instances are used to convert between Haskell types and database types.\n\nIf you do not want to use `Enum` types for your enumeration fields, you have to implement `Convertible` instances manually:\n\n```haskell\ndata BookCategory = Fiction | Travel | Arts | Science | History | Biography | Other\n  deriving (Generic, Show, Read)\n\ninstance Convertible BookCategory SqlValue where\n  safeConvert = Right . toSql . show\n  \ninstance Convertible SqlValue BookCategory where\n  safeConvert = Right . read . fromSql  \n```\n\n## Handling embedded Objects\n\nSay we have a data type `Article` with a field of type `Author`:\n\n```haskell\ndata Article = Article\n  { articleID :: Int,\n    title     :: String,\n    author    :: Author,\n    year      :: Int\n  }\n  deriving (Generic, Show, Eq)\n\ndata Author = Author\n  { authorID :: Int,\n    name     :: String,\n    address  :: String\n  }\n  deriving (Generic, Show, Eq)  \n```\n\nIf we don't want to store the `Author` as a separate table, we can use the following approach to embed the `Author` into the `Article` table:\n\n```haskell\ninstance Entity Article where\n  -- in the fields to column mapping we specify that all fields of the \n  -- Author type are also mapped to columns of the Article table:\n  fieldsToColumns :: [(String, String)]\n  fieldsToColumns = [(\"articleID\", \"articleID\"),\n                       (\"title\", \"title\"), \n                       (\"authorID\", \"authorID\"), \n                       (\"authorName\", \"authorName\"), \n                       (\"authorAddress\", \"authorAddress\"),\n                       (\"year\", \"year\")\n                    ]\n\n  -- in fromRow we have to manually construct the Author object from the \n  -- respective columns of the Article table and insert it \n  -- into the Article object:\n  fromRow _conn row = return $ Article (col 0) (col 1) author (col 5)\n    where\n      col i = fromSql (row !! i)\n      author = Author (col 2) (col 3) (col 4)\n\n  -- in toRow we have to manually extract the fields of the Author object\n  -- and insert them into the respective columns of the Article table:\n  toRow _conn a = return [toSql (articleID a), toSql (title a), toSql authID, toSql authorName, toSql authorAddress, toSql (year a)]\n    where \n      authID = authorID (author a)\n      authorName = name (author a)\n      authorAddress = address (author a)\n```\n\n## Handling 1:1 references\n\nIf we have the same data types as in the previous example, but we want to store the `Author` in a separate table, we can use the following approach:\n\n```haskell\ndata Article = Article\n  { articleID :: Int,\n    title     :: String,\n    author    :: Author,\n    year      :: Int\n  }\n  deriving (Generic, Show, Eq)\n\ndata Author = Author\n  { authorID :: Int,\n    name     :: String,\n    address  :: String\n  }\n  deriving (Generic, Entity, Show, Eq)\n\n\ninstance Entity Article where\n  fieldsToColumns :: [(String, String)]                      -- ommitting the author field,\n  fieldsToColumns =                                          -- as this can not be mapped to a single column\n    [ (\"articleID\", \"articleID\"),                            -- instead we invent a new column authorID         \n      (\"title\", \"title\"),\n      (\"authorID\", \"authorID\"),\n      (\"year\", \"year\")\n    ]\n\n  fromRow :: Conn -\u003e [SqlValue] -\u003e IO Article\n  fromRow conn row = do    \n    authorById \u003c- fromJust \u003c$\u003e selectById conn (row !! 2)  -- load author by foreign key\n    return $ rawArticle {author = authorById}              -- add author to article\n    where\n      rawArticle = Article (col 0) (col 1)                 -- create article from row, \n                           (Author (col 2) \"\" \"\") (col 3)  -- using a dummy author\n        where\n          col i = fromSql (row !! i)\n\n  toRow :: Conn -\u003e Article -\u003e IO [SqlValue]\n  toRow conn a = do\n    upsert conn (author a)                                  -- persist author first\n    return [toSql (articleID a), toSql (title a),           -- return row for article table where \n            toSql $ authorID (author a), toSql (year a)]    -- authorID is foreign key to author table \n```\n\nPersisting the `Author`as a side effect in `toRow` may sound like an *interesting* idea...\nThis step is optional. But then the user has to make sure that the `Author` is persisted before the `Article` is persisted.\n\n\n## Handling 1:n references\n\nNow let's change the previous example by having a list of Articles in the `Author` type:\n\n```haskell\ndata Author = Author\n  { authorID :: Int,\n    name     :: String,\n    address  :: String,\n    articles :: [Article]\n  }\n  deriving (Generic, Show, Eq)\n\ndata Article = Article\n  { articleID :: Int,\n    title     :: String,\n    authorId  :: Int,\n    year      :: Int\n  }\n  deriving (Generic, Entity, Show, Eq)\n```\n\nSo now we have a `1:n` relationship between `Author` and `Article`. \n\nWe can handle this situation by using the following instance declaration for `Author`:\n\n```haskell\ninstance Entity Author where\n  fieldsToColumns :: [(String, String)]                   -- ommitting the articles field, \n  fieldsToColumns =                                       -- as this can not be mapped to a single column\n    [ (\"authorID\", \"authorID\"),\n      (\"name\", \"name\"),\n      (\"address\", \"address\")\n    ]\n\n  fromRow :: Conn -\u003e [SqlValue] -\u003e IO Author\n  fromRow conn row = do\n    let authID = head row                                  -- authorID is the first column\n    articlesBy \u003c- select conn (field \"authorId\" =. authID) -- retrieve all articles by this author\n    return rawAuthor {articles = articlesBy}               -- add the articles to the author\n    where\n      rawAuthor = Author (col 0) (col 1) (col 2) []        -- create the author from row (w/o articles)\n      col i = fromSql (row !! i)                           -- helper function to convert SqlValue to Haskell type\n\n  toRow :: Conn -\u003e Author -\u003e IO [SqlValue]\n  toRow conn a = do\n    mapM_ (upsert conn) (articles a)                      -- persist all articles of this author (update or insert)\n    return [toSql (authorID a),                           -- return the author as a list of SqlValues\n            toSql (name a), toSql (address a)]\n```\n\nPersisting all articles of an author as a side effect during the conversion of the author to a row may seem *special*...\nYou can ommit this step. But then you have to persist the articles manually before persisting the author.\n\n## Performing queries with the Query DSL\n\nThe library provides a simple DSL for performing `SELECT`queries. The `select` function \n\n```haskell\nselect :: forall a. (Entity a) =\u003e Conn -\u003e WhereClauseExpr -\u003e IO [a]\n```\n\nThis function retrieves all entities of type `a` that match some query criteria.\nThe function takes an HDBC connection (wrapped in a `Conn`) and a `WhereClauseExpr` as parameters.\nThe function returns a (possibly empty) list of all matching entities.\n\nThe `WhereClauseExpr` is constructed using a small set of functions and infix operators.\n\nThere are a set of infix operators `(=.), (\u003e.), (\u003c.), (\u003e=.), (\u003c=.), (\u003c\u003e.), like, between, in', contains` that define field comparisons:\n\n```haskell\nfield \"name\" =. \"John\"\n\nfield \"age\" \u003e=. 18\n\nfield \"age\" `between` (18, 30)\n\nfield \"name\" `like` \"J%\"\n\nfield \"name\" `in'` [\"John\", \"Jane\"]\n```\n\nThen we have three function `isNull`, `allEntries` and `byId` that also define simple `WHERE` clauses:\n\n```haskell\nisNull (field \"name\") -- matches all entries where the name field is NULL\n\nbyId 42               -- matches the entry where the primary key column has the value 42\n\nallEntries            -- matches all entries of the table\n```\n\nIt is also possible to apply SQL functions to fields:\n\n```haskell\nlower = sqlFun \"LOWER\" -- define a function that applies the SQL function LOWER to a field\n\nlower(field \"name\") =. \"all lowercase\"\n```\n\nThese field-wise comparisons can be combined using the logical operators `\u0026\u0026.`, `||.` and `not'`:\n\n```haskell\n(field \"name\" `like` \"J%\") \u0026\u0026. (field \"age\" \u003e=. 18)\n\n(field \"name\" =. \"John\") ||. (field \"name\" =. \"Jane\")\n\nnot' (field \"name\" =. \"John\")\n```\n\nThe `select` function will then use the `WhereClauseExpr` constructed from these operators and functions to generate a SQL query that retrieves all matching entities:\n\n```haskell\n\nageField :: Field\nageField = field \"age\"\n\nthirtySomethings \u003c- select @Person conn (ageField `between` (30, 39))\n```\n\nIt is also possible to add `ORDER BY` and `LIMIT` clauses to the query:\n\n```haskell\nsortedPersons \u003c- select @Person conn (allEntries `orderBy` [(ageField,ASC), (nameField,DESC)])\n\nlimitedPersons \u003c- select @Person conn (allEntries `limit` 25)\n\npageOfPersons \u003c- select @Person conn (allEntries `limitOffset` (100,10))\n```\n\nYou will find more examples in the [test suite](https://github.com/thma/generic-persistence/blob/main/test/QuerySpec.hs).\n\n\n## Integrating user defined queries\n\nAs we have seen in the previous section, the library provides two functions `select` and `selectById` to query the database for entities.\n\nIf you want to use more complex queries, you can integrate HDBC SQL queries by using the `entitiesFromRows` function as in the following example:\n\n```haskell\nmain :: IO ()\nmain = do\n  -- connect to a database\n  conn \u003c- connect SQLite \u003c$\u003e connectSqlite3 \":memory:\" \n\n  -- initialize Person table\n  setupTableFor @Person conn\n\n  let alice = Person 1 \"Alice\" 25 \"123 Main St\"\n      bob = Person 2 \"Bob\" 30 \"456 Elm St\"\n      charlie = Person 3 \"Charlie\" 35 \"789 Pine St\"\n      dave = Person 4 \"Dave\" 40 \"1011 Oak St\"\n      eve = Person 5 \"Eve\" 45 \"1213 Maple St\"\n      frank = Person 6 \"Frank\" 50 \"1415 Walnut St\"\n      people = [alice, bob, charlie, dave, eve, frank]\n      \n  -- insert all persons into the database\n  insertMany conn people\n\n  -- perform a custom query with HDBC\n  stmt = \"SELECT * FROM Person WHERE age \u003e= ? ORDER BY age ASC\"\n  resultRows \u003c- quickQuery conn stmt [toSql (40 :: Int)]\n\n  -- convert the resulting rows into a list of Person objects\n  fourtplussers \u003c- entitiesFromRows @Person conn resultRows\n  print fourtplussers\n```\n\nOf course this approach is not type safe. It is up to the user to make sure that the query returns the correct columns. \n\n## The `Conn` Connection Type\n\nThe `Conn` type is a wrapper around an `IConnection` obtained from an HDBC backend driver like `HDBC-sqlite3` or `hdbc-postgresql`. It is used to pass the connection to the database to *Generic-Persistence*. All functions of the library that require a database connection take a `Conn` as an argument.\n\nHDBC provides a very similar type called `ConnectionWrapper`. The main reason for such a wrapper type is to simplify the type signatures of the library functions. \n\nIn addition, the `Conn` type provides additional database related information that is not available in the `ConnectionWrapper` type. For example, the `Conn` type contains the name of the database driver that is used. This information can be used to generate the correct SQL statements for different database backends.\n`Conn` also carries a flag that indicates whether implicit commits should be used by the library. This flag is set to `True` by default. If you want to use explicit commits, you can set the flag to `False` by modifying the `Conn` value:\n  \n```haskell\nc \u003c- connect SQLite \u003c$\u003e connectSqlite3 \":memory:\"\nlet conn = c {implicitCommit = False}\n```\n\n## Connection Pooling\n\nThe library provides a simple connection pool for managing database connections. \nThis is a must in multi-threaded environments where multiple threads may need to access the database at the same time. A typical use case is a REST service that uses a database to store its data. \n\nThe connection Pool is implemented based on the [resource-pool](https://hackage.haskell.org/package/resource-pool) library. `generic-persistence` exposes a `ConnectionPool` type and two function `createConnPool` and `withResource` to create and use a connection pool.\n\nThe following example shows how to create a connection pool and how to use it to perform a database query:\n\n```haskell\n\nsqlLitePool :: FilePath -\u003e IO ConnectionPool\nsqlLitePool sqlLiteFile = createConnPool SQLite sqlLiteFile connectSqlite3 10 100\n\nmain :: IO ()\nmain = do\n  connPool \u003c- sqlLitePool \":memory:\" \n  let alice = Person 123456 \"Alice\" 25 \"123 Main St\"\n  withResource connPool $ \\conn -\u003e do\n    setupTableFor @Person conn\n    insert conn alice\n    allPersons \u003c- select conn allEntries :: IO [Person]\n    print allPersons\n```\n\nYou'll find a more complete example in the [servant-gp repo](https://github.com/thma/servant-gp/blob/main/src/ServerUtils.hs#L45).\nThere I have set up a sample REST service based on Servant that uses *Generic-Persistence* and a connection pool to manage the database connections.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthma%2Fgeneric-persistence","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthma%2Fgeneric-persistence","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthma%2Fgeneric-persistence/lists"}