Repository files navigation

PG-Entity CI-badgedocssimple-haskell

This library is a pleasant layer on top of postgresql-simple to safely expand the fields of a table when writing SQL queries.
It aims to be a convenient middle-ground between rigid ORMs and hand-rolled SQL query strings. Here is its philosophy:

  • The serialisation/deserialisation part is left to the consumer, so you have to go with your own FromRow/ToRow instances. You are encouraged to adopt data types that model your business, rather than restrict yourself within the limits of what an SQL schema can represent. Use an intermediate Data Access Object (DAO) that can easily be serialised and deserialised to and from a SQL schema, to and from which you will morph your business data-types.
  • Illegal states are made harder (but not impossible) to represent. Generic deriving of entities is encouraged, and quasi-quoters are provided to denote fields in a safer way.
  • Escape hatches are provided at every level. The types that are manipulated are Query for which an IsString instance exists. Don't force yourself to use the higher-level API if the lower-level combinators work for you, and if those don't either, “Just Write SQL”™.

Its dependency footprint is optimised for my own setups, and as such it makes use of text, vector and pg-transact.

Table of Contents

Installation

At present time, pg-entity is published on Hackage but not on Stackage. To use it in your projects, add it in your cabal file like this:

pg-entity ^>= 0.0

or in your stack.yaml file:

extra-deps:
- pg-entity-0.0.1.0

The following GHC versions are supported:

  • 8.8
  • 8.10
  • 9.0

Documentation

This library aims to be thoroughly tested, by the means of Oleg Grerus' cabal-docspec and more traditional tests for database roundtrips.

I aim to produce and maintain a decent documentation, therefore do not hesitate to raise an issue if you feel that something is badly explained and should be improved.

You will find the Tutorial here, and you will find below a short showcase of the library.

Usage

The idea is to implement the Entity typeclass for the datatypes that represent your PostgreSQL table.

-- Traditional list & string syntax
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
-- Quasi-quoter to construct SQL expressions
{-# LANGUAGE QuasiQuotes #-}
-- Deriving machinery
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingVia #-}
importData.UUID (UUID)
importData.Vector (Vector)
importDatabase.PostgreSQL.Simple.SqlQQimportDatabase.PostgreSQL.Entity-- This is our Primary Key newtype. It is wrapped in a newtype to make-- it impossible to mitake with a plain `UUID`, but we still want to-- benefit from the pre-existing typeclass instances that exist for-- `UUID`. You can read the last two lines as:-- > We use the definitions posessed by `UUID` for our own newtype.newtypeJobId=JobId{getJobId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID-- A straightforward table definition, which lets us use-- the DerivingVia mechanism to declare the table name-- in the `deriving` clause, and infer the fields and primary key.-- The field names will be converted to snake_case.dataJob=Job{jobId::JobId
, lockedAt::UTCTime
, jobName::Text}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
derivingEntityvia (GenericEntity '[TableName"jobs"] Job)
-- In the above deriving clause, we only had to specify the table name in order to pluralise it,-- leaving the guessing of the primary key and the table names to the library.-- Below is a richer table definition that needs some type annotations to help PostgreSQL.-- We will have to write out the full instance by handnewtypeBagId=BagId{getBagId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID--| This is a PostgreSQL Enum, which needs to be marked as such in SQL type annotations.dataProperties=P1 | P2 | P3derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
dataBag=Bag{bagId::BagId
, someField::VectorUUID
, properties::VectorProperties}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
instanceEntityBagwhere
tableName ="bags"
primaryKey = [field| bag_id |]
fields = [ [field| bag_id |]
, [field| some_field :: uuid[] |]
, [field| properties :: properties[] |]
]
-- You can write specialised functions to remove the noise of Type ApplicationsinsertBag::Bag->DBTIO()
insertBag = insert -- `insert` will be specialised to `Bag`-- And you can insert raw SQL through postgresql-simpleisJobLocked::Int->DBTIO (OnlyBool)
isJobLocked jobId = queryOne Select q (Only jobId)
where q = [sql| SELECT
CASE WHEN locked_at IS NULL then false
ELSE true
END
FROM jobs WHERE job_id = ?
|]

For more examples, see the BlogPost module for the data-type that is used throughout the tests and doctests.

Escape hatches

Safe SQL generation is a complex subject, and it is far from being the objective of this library. The main topic it addresses is listing the fields of a table, which is definitely something easier. This is why every level of this wrapper is fully exposed, so that you can drop down a level at your convience.

It is my personal belief, firmly rooted in experience, that we should not aim to produce statically-checked SQL and have it "verified" by the compiler. The techniques that would allow that in Haskell are still far from being optimised and ergonomic. As such, this library makes no effort to produce semantically valid SQL queries, because one would have to encode the semantics of SQL in the type system (or in a rule engine of some sort), and this is clearly not the kind of things I want to spend my youth on.

Each function is tested for its output with doctests, and the ones that cannot (due to database connections) are tested in the more traditional test-suite.

The conclusion is : Test your DB queries. Test the encoding/decoding. Make roundtrip tests for your data-structures.

Acknowledgements

I wish to thank

  • Clément Delafargue, whose anorm-pg-entity library and its initial port in Haskell are the spiritual parents of this library
  • Koz Ross, for his piercing eyes and his immense patience
  • Joe Kachmar, who enlightened me many times

About

A pleasant PostgreSQL database layer for Haskell

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

PG-Entity CI-badgedocssimple-haskell

This library is a pleasant layer on top of postgresql-simple to safely expand the fields of a table when writing SQL queries.
It aims to be a convenient middle-ground between rigid ORMs and hand-rolled SQL query strings. Here is its philosophy:

  • The serialisation/deserialisation part is left to the consumer, so you have to go with your own FromRow/ToRow instances. You are encouraged to adopt data types that model your business, rather than restrict yourself within the limits of what an SQL schema can represent. Use an intermediate Data Access Object (DAO) that can easily be serialised and deserialised to and from a SQL schema, to and from which you will morph your business data-types.
  • Illegal states are made harder (but not impossible) to represent. Generic deriving of entities is encouraged, and quasi-quoters are provided to denote fields in a safer way.
  • Escape hatches are provided at every level. The types that are manipulated are Query for which an IsString instance exists. Don't force yourself to use the higher-level API if the lower-level combinators work for you, and if those don't either, “Just Write SQL”™.

Its dependency footprint is optimised for my own setups, and as such it makes use of text, vector and pg-transact.

Table of Contents

Installation

At present time, pg-entity is published on Hackage but not on Stackage. To use it in your projects, add it in your cabal file like this:

pg-entity ^>= 0.0

or in your stack.yaml file:

extra-deps:
- pg-entity-0.0.1.0

The following GHC versions are supported:

  • 8.8
  • 8.10
  • 9.0

Documentation

This library aims to be thoroughly tested, by the means of Oleg Grerus' cabal-docspec and more traditional tests for database roundtrips.

I aim to produce and maintain a decent documentation, therefore do not hesitate to raise an issue if you feel that something is badly explained and should be improved.

You will find the Tutorial here, and you will find below a short showcase of the library.

Usage

The idea is to implement the Entity typeclass for the datatypes that represent your PostgreSQL table.

-- Traditional list & string syntax
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
-- Quasi-quoter to construct SQL expressions
{-# LANGUAGE QuasiQuotes #-}
-- Deriving machinery
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingVia #-}
importData.UUID (UUID)
importData.Vector (Vector)
importDatabase.PostgreSQL.Simple.SqlQQimportDatabase.PostgreSQL.Entity-- This is our Primary Key newtype. It is wrapped in a newtype to make-- it impossible to mitake with a plain `UUID`, but we still want to-- benefit from the pre-existing typeclass instances that exist for-- `UUID`. You can read the last two lines as:-- > We use the definitions posessed by `UUID` for our own newtype.newtypeJobId=JobId{getJobId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID-- A straightforward table definition, which lets us use-- the DerivingVia mechanism to declare the table name-- in the `deriving` clause, and infer the fields and primary key.-- The field names will be converted to snake_case.dataJob=Job{jobId::JobId
, lockedAt::UTCTime
, jobName::Text}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
derivingEntityvia (GenericEntity '[TableName"jobs"] Job)
-- In the above deriving clause, we only had to specify the table name in order to pluralise it,-- leaving the guessing of the primary key and the table names to the library.-- Below is a richer table definition that needs some type annotations to help PostgreSQL.-- We will have to write out the full instance by handnewtypeBagId=BagId{getBagId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID--| This is a PostgreSQL Enum, which needs to be marked as such in SQL type annotations.dataProperties=P1 | P2 | P3derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
dataBag=Bag{bagId::BagId
, someField::VectorUUID
, properties::VectorProperties}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
instanceEntityBagwhere
tableName ="bags"
primaryKey = [field| bag_id |]
fields = [ [field| bag_id |]
, [field| some_field :: uuid[] |]
, [field| properties :: properties[] |]
]
-- You can write specialised functions to remove the noise of Type ApplicationsinsertBag::Bag->DBTIO()
insertBag = insert -- `insert` will be specialised to `Bag`-- And you can insert raw SQL through postgresql-simpleisJobLocked::Int->DBTIO (OnlyBool)
isJobLocked jobId = queryOne Select q (Only jobId)
where q = [sql| SELECT
CASE WHEN locked_at IS NULL then false
ELSE true
END
FROM jobs WHERE job_id = ?
|]

For more examples, see the BlogPost module for the data-type that is used throughout the tests and doctests.

Escape hatches

Safe SQL generation is a complex subject, and it is far from being the objective of this library. The main topic it addresses is listing the fields of a table, which is definitely something easier. This is why every level of this wrapper is fully exposed, so that you can drop down a level at your convience.

It is my personal belief, firmly rooted in experience, that we should not aim to produce statically-checked SQL and have it "verified" by the compiler. The techniques that would allow that in Haskell are still far from being optimised and ergonomic. As such, this library makes no effort to produce semantically valid SQL queries, because one would have to encode the semantics of SQL in the type system (or in a rule engine of some sort), and this is clearly not the kind of things I want to spend my youth on.

Each function is tested for its output with doctests, and the ones that cannot (due to database connections) are tested in the more traditional test-suite.

The conclusion is : Test your DB queries. Test the encoding/decoding. Make roundtrip tests for your data-structures.

Acknowledgements

I wish to thank

  • Clément Delafargue, whose anorm-pg-entity library and its initial port in Haskell are the spiritual parents of this library
  • Koz Ross, for his piercing eyes and his immense patience
  • Joe Kachmar, who enlightened me many times

About

A pleasant PostgreSQL database layer for Haskell

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

PG-Entity CI-badgedocssimple-haskell

This library is a pleasant layer on top of postgresql-simple to safely expand the fields of a table when writing SQL queries.
It aims to be a convenient middle-ground between rigid ORMs and hand-rolled SQL query strings. Here is its philosophy:

  • The serialisation/deserialisation part is left to the consumer, so you have to go with your own FromRow/ToRow instances. You are encouraged to adopt data types that model your business, rather than restrict yourself within the limits of what an SQL schema can represent. Use an intermediate Data Access Object (DAO) that can easily be serialised and deserialised to and from a SQL schema, to and from which you will morph your business data-types.
  • Illegal states are made harder (but not impossible) to represent. Generic deriving of entities is encouraged, and quasi-quoters are provided to denote fields in a safer way.
  • Escape hatches are provided at every level. The types that are manipulated are Query for which an IsString instance exists. Don't force yourself to use the higher-level API if the lower-level combinators work for you, and if those don't either, “Just Write SQL”™.

Its dependency footprint is optimised for my own setups, and as such it makes use of text, vector and pg-transact.

Table of Contents

Installation

At present time, pg-entity is published on Hackage but not on Stackage. To use it in your projects, add it in your cabal file like this:

pg-entity ^>= 0.0

or in your stack.yaml file:

extra-deps:
- pg-entity-0.0.1.0

The following GHC versions are supported:

  • 8.8
  • 8.10
  • 9.0

Documentation

This library aims to be thoroughly tested, by the means of Oleg Grerus' cabal-docspec and more traditional tests for database roundtrips.

I aim to produce and maintain a decent documentation, therefore do not hesitate to raise an issue if you feel that something is badly explained and should be improved.

You will find the Tutorial here, and you will find below a short showcase of the library.

Usage

The idea is to implement the Entity typeclass for the datatypes that represent your PostgreSQL table.

-- Traditional list & string syntax
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
-- Quasi-quoter to construct SQL expressions
{-# LANGUAGE QuasiQuotes #-}
-- Deriving machinery
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingVia #-}
importData.UUID (UUID)
importData.Vector (Vector)
importDatabase.PostgreSQL.Simple.SqlQQimportDatabase.PostgreSQL.Entity-- This is our Primary Key newtype. It is wrapped in a newtype to make-- it impossible to mitake with a plain `UUID`, but we still want to-- benefit from the pre-existing typeclass instances that exist for-- `UUID`. You can read the last two lines as:-- > We use the definitions posessed by `UUID` for our own newtype.newtypeJobId=JobId{getJobId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID-- A straightforward table definition, which lets us use-- the DerivingVia mechanism to declare the table name-- in the `deriving` clause, and infer the fields and primary key.-- The field names will be converted to snake_case.dataJob=Job{jobId::JobId
, lockedAt::UTCTime
, jobName::Text}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
derivingEntityvia (GenericEntity '[TableName"jobs"] Job)
-- In the above deriving clause, we only had to specify the table name in order to pluralise it,-- leaving the guessing of the primary key and the table names to the library.-- Below is a richer table definition that needs some type annotations to help PostgreSQL.-- We will have to write out the full instance by handnewtypeBagId=BagId{getBagId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID--| This is a PostgreSQL Enum, which needs to be marked as such in SQL type annotations.dataProperties=P1 | P2 | P3derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
dataBag=Bag{bagId::BagId
, someField::VectorUUID
, properties::VectorProperties}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
instanceEntityBagwhere
tableName ="bags"
primaryKey = [field| bag_id |]
fields = [ [field| bag_id |]
, [field| some_field :: uuid[] |]
, [field| properties :: properties[] |]
]
-- You can write specialised functions to remove the noise of Type ApplicationsinsertBag::Bag->DBTIO()
insertBag = insert -- `insert` will be specialised to `Bag`-- And you can insert raw SQL through postgresql-simpleisJobLocked::Int->DBTIO (OnlyBool)
isJobLocked jobId = queryOne Select q (Only jobId)
where q = [sql| SELECT
CASE WHEN locked_at IS NULL then false
ELSE true
END
FROM jobs WHERE job_id = ?
|]

For more examples, see the BlogPost module for the data-type that is used throughout the tests and doctests.

Escape hatches

Safe SQL generation is a complex subject, and it is far from being the objective of this library. The main topic it addresses is listing the fields of a table, which is definitely something easier. This is why every level of this wrapper is fully exposed, so that you can drop down a level at your convience.

It is my personal belief, firmly rooted in experience, that we should not aim to produce statically-checked SQL and have it "verified" by the compiler. The techniques that would allow that in Haskell are still far from being optimised and ergonomic. As such, this library makes no effort to produce semantically valid SQL queries, because one would have to encode the semantics of SQL in the type system (or in a rule engine of some sort), and this is clearly not the kind of things I want to spend my youth on.

Each function is tested for its output with doctests, and the ones that cannot (due to database connections) are tested in the more traditional test-suite.

The conclusion is : Test your DB queries. Test the encoding/decoding. Make roundtrip tests for your data-structures.

Acknowledgements

I wish to thank

  • Clément Delafargue, whose anorm-pg-entity library and its initial port in Haskell are the spiritual parents of this library
  • Koz Ross, for his piercing eyes and his immense patience
  • Joe Kachmar, who enlightened me many times

About

A pleasant PostgreSQL database layer for Haskell

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

PG-Entity CI-badgedocssimple-haskell

This library is a pleasant layer on top of postgresql-simple to safely expand the fields of a table when writing SQL queries.
It aims to be a convenient middle-ground between rigid ORMs and hand-rolled SQL query strings. Here is its philosophy:

  • The serialisation/deserialisation part is left to the consumer, so you have to go with your own FromRow/ToRow instances. You are encouraged to adopt data types that model your business, rather than restrict yourself within the limits of what an SQL schema can represent. Use an intermediate Data Access Object (DAO) that can easily be serialised and deserialised to and from a SQL schema, to and from which you will morph your business data-types.
  • Illegal states are made harder (but not impossible) to represent. Generic deriving of entities is encouraged, and quasi-quoters are provided to denote fields in a safer way.
  • Escape hatches are provided at every level. The types that are manipulated are Query for which an IsString instance exists. Don't force yourself to use the higher-level API if the lower-level combinators work for you, and if those don't either, “Just Write SQL”™.

Its dependency footprint is optimised for my own setups, and as such it makes use of text, vector and pg-transact.

Table of Contents

Installation

At present time, pg-entity is published on Hackage but not on Stackage. To use it in your projects, add it in your cabal file like this:

pg-entity ^>= 0.0

or in your stack.yaml file:

extra-deps:
- pg-entity-0.0.1.0

The following GHC versions are supported:

  • 8.8
  • 8.10
  • 9.0

Documentation

This library aims to be thoroughly tested, by the means of Oleg Grerus' cabal-docspec and more traditional tests for database roundtrips.

I aim to produce and maintain a decent documentation, therefore do not hesitate to raise an issue if you feel that something is badly explained and should be improved.

You will find the Tutorial here, and you will find below a short showcase of the library.

Usage

The idea is to implement the Entity typeclass for the datatypes that represent your PostgreSQL table.

-- Traditional list & string syntax
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
-- Quasi-quoter to construct SQL expressions
{-# LANGUAGE QuasiQuotes #-}
-- Deriving machinery
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingVia #-}
importData.UUID (UUID)
importData.Vector (Vector)
importDatabase.PostgreSQL.Simple.SqlQQimportDatabase.PostgreSQL.Entity-- This is our Primary Key newtype. It is wrapped in a newtype to make-- it impossible to mitake with a plain `UUID`, but we still want to-- benefit from the pre-existing typeclass instances that exist for-- `UUID`. You can read the last two lines as:-- > We use the definitions posessed by `UUID` for our own newtype.newtypeJobId=JobId{getJobId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID-- A straightforward table definition, which lets us use-- the DerivingVia mechanism to declare the table name-- in the `deriving` clause, and infer the fields and primary key.-- The field names will be converted to snake_case.dataJob=Job{jobId::JobId
, lockedAt::UTCTime
, jobName::Text}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
derivingEntityvia (GenericEntity '[TableName"jobs"] Job)
-- In the above deriving clause, we only had to specify the table name in order to pluralise it,-- leaving the guessing of the primary key and the table names to the library.-- Below is a richer table definition that needs some type annotations to help PostgreSQL.-- We will have to write out the full instance by handnewtypeBagId=BagId{getBagId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID--| This is a PostgreSQL Enum, which needs to be marked as such in SQL type annotations.dataProperties=P1 | P2 | P3derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
dataBag=Bag{bagId::BagId
, someField::VectorUUID
, properties::VectorProperties}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
instanceEntityBagwhere
tableName ="bags"
primaryKey = [field| bag_id |]
fields = [ [field| bag_id |]
, [field| some_field :: uuid[] |]
, [field| properties :: properties[] |]
]
-- You can write specialised functions to remove the noise of Type ApplicationsinsertBag::Bag->DBTIO()
insertBag = insert -- `insert` will be specialised to `Bag`-- And you can insert raw SQL through postgresql-simpleisJobLocked::Int->DBTIO (OnlyBool)
isJobLocked jobId = queryOne Select q (Only jobId)
where q = [sql| SELECT
CASE WHEN locked_at IS NULL then false
ELSE true
END
FROM jobs WHERE job_id = ?
|]

For more examples, see the BlogPost module for the data-type that is used throughout the tests and doctests.

Escape hatches

Safe SQL generation is a complex subject, and it is far from being the objective of this library. The main topic it addresses is listing the fields of a table, which is definitely something easier. This is why every level of this wrapper is fully exposed, so that you can drop down a level at your convience.

It is my personal belief, firmly rooted in experience, that we should not aim to produce statically-checked SQL and have it "verified" by the compiler. The techniques that would allow that in Haskell are still far from being optimised and ergonomic. As such, this library makes no effort to produce semantically valid SQL queries, because one would have to encode the semantics of SQL in the type system (or in a rule engine of some sort), and this is clearly not the kind of things I want to spend my youth on.

Each function is tested for its output with doctests, and the ones that cannot (due to database connections) are tested in the more traditional test-suite.

The conclusion is : Test your DB queries. Test the encoding/decoding. Make roundtrip tests for your data-structures.

Acknowledgements

I wish to thank

  • Clément Delafargue, whose anorm-pg-entity library and its initial port in Haskell are the spiritual parents of this library
  • Koz Ross, for his piercing eyes and his immense patience
  • Joe Kachmar, who enlightened me many times

About

A pleasant PostgreSQL database layer for Haskell

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

PG-Entity CI-badgedocssimple-haskell

This library is a pleasant layer on top of postgresql-simple to safely expand the fields of a table when writing SQL queries.
It aims to be a convenient middle-ground between rigid ORMs and hand-rolled SQL query strings. Here is its philosophy:

  • The serialisation/deserialisation part is left to the consumer, so you have to go with your own FromRow/ToRow instances. You are encouraged to adopt data types that model your business, rather than restrict yourself within the limits of what an SQL schema can represent. Use an intermediate Data Access Object (DAO) that can easily be serialised and deserialised to and from a SQL schema, to and from which you will morph your business data-types.
  • Illegal states are made harder (but not impossible) to represent. Generic deriving of entities is encouraged, and quasi-quoters are provided to denote fields in a safer way.
  • Escape hatches are provided at every level. The types that are manipulated are Query for which an IsString instance exists. Don't force yourself to use the higher-level API if the lower-level combinators work for you, and if those don't either, “Just Write SQL”™.

Its dependency footprint is optimised for my own setups, and as such it makes use of text, vector and pg-transact.

Table of Contents

Installation

At present time, pg-entity is published on Hackage but not on Stackage. To use it in your projects, add it in your cabal file like this:

pg-entity ^>= 0.0

or in your stack.yaml file:

extra-deps:
- pg-entity-0.0.1.0

The following GHC versions are supported:

  • 8.8
  • 8.10
  • 9.0

Documentation

This library aims to be thoroughly tested, by the means of Oleg Grerus' cabal-docspec and more traditional tests for database roundtrips.

I aim to produce and maintain a decent documentation, therefore do not hesitate to raise an issue if you feel that something is badly explained and should be improved.

You will find the Tutorial here, and you will find below a short showcase of the library.

Usage

The idea is to implement the Entity typeclass for the datatypes that represent your PostgreSQL table.

-- Traditional list & string syntax
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
-- Quasi-quoter to construct SQL expressions
{-# LANGUAGE QuasiQuotes #-}
-- Deriving machinery
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingVia #-}
importData.UUID (UUID)
importData.Vector (Vector)
importDatabase.PostgreSQL.Simple.SqlQQimportDatabase.PostgreSQL.Entity-- This is our Primary Key newtype. It is wrapped in a newtype to make-- it impossible to mitake with a plain `UUID`, but we still want to-- benefit from the pre-existing typeclass instances that exist for-- `UUID`. You can read the last two lines as:-- > We use the definitions posessed by `UUID` for our own newtype.newtypeJobId=JobId{getJobId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID-- A straightforward table definition, which lets us use-- the DerivingVia mechanism to declare the table name-- in the `deriving` clause, and infer the fields and primary key.-- The field names will be converted to snake_case.dataJob=Job{jobId::JobId
, lockedAt::UTCTime
, jobName::Text}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
derivingEntityvia (GenericEntity '[TableName"jobs"] Job)
-- In the above deriving clause, we only had to specify the table name in order to pluralise it,-- leaving the guessing of the primary key and the table names to the library.-- Below is a richer table definition that needs some type annotations to help PostgreSQL.-- We will have to write out the full instance by handnewtypeBagId=BagId{getBagId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID--| This is a PostgreSQL Enum, which needs to be marked as such in SQL type annotations.dataProperties=P1 | P2 | P3derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
dataBag=Bag{bagId::BagId
, someField::VectorUUID
, properties::VectorProperties}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
instanceEntityBagwhere
tableName ="bags"
primaryKey = [field| bag_id |]
fields = [ [field| bag_id |]
, [field| some_field :: uuid[] |]
, [field| properties :: properties[] |]
]
-- You can write specialised functions to remove the noise of Type ApplicationsinsertBag::Bag->DBTIO()
insertBag = insert -- `insert` will be specialised to `Bag`-- And you can insert raw SQL through postgresql-simpleisJobLocked::Int->DBTIO (OnlyBool)
isJobLocked jobId = queryOne Select q (Only jobId)
where q = [sql| SELECT
CASE WHEN locked_at IS NULL then false
ELSE true
END
FROM jobs WHERE job_id = ?
|]

For more examples, see the BlogPost module for the data-type that is used throughout the tests and doctests.

Escape hatches

Safe SQL generation is a complex subject, and it is far from being the objective of this library. The main topic it addresses is listing the fields of a table, which is definitely something easier. This is why every level of this wrapper is fully exposed, so that you can drop down a level at your convience.

It is my personal belief, firmly rooted in experience, that we should not aim to produce statically-checked SQL and have it "verified" by the compiler. The techniques that would allow that in Haskell are still far from being optimised and ergonomic. As such, this library makes no effort to produce semantically valid SQL queries, because one would have to encode the semantics of SQL in the type system (or in a rule engine of some sort), and this is clearly not the kind of things I want to spend my youth on.

Each function is tested for its output with doctests, and the ones that cannot (due to database connections) are tested in the more traditional test-suite.

The conclusion is : Test your DB queries. Test the encoding/decoding. Make roundtrip tests for your data-structures.

Acknowledgements

I wish to thank

  • Clément Delafargue, whose anorm-pg-entity library and its initial port in Haskell are the spiritual parents of this library
  • Koz Ross, for his piercing eyes and his immense patience
  • Joe Kachmar, who enlightened me many times

About

A pleasant PostgreSQL database layer for Haskell

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

PG-Entity CI-badgedocssimple-haskell

This library is a pleasant layer on top of postgresql-simple to safely expand the fields of a table when writing SQL queries.
It aims to be a convenient middle-ground between rigid ORMs and hand-rolled SQL query strings. Here is its philosophy:

  • The serialisation/deserialisation part is left to the consumer, so you have to go with your own FromRow/ToRow instances. You are encouraged to adopt data types that model your business, rather than restrict yourself within the limits of what an SQL schema can represent. Use an intermediate Data Access Object (DAO) that can easily be serialised and deserialised to and from a SQL schema, to and from which you will morph your business data-types.
  • Illegal states are made harder (but not impossible) to represent. Generic deriving of entities is encouraged, and quasi-quoters are provided to denote fields in a safer way.
  • Escape hatches are provided at every level. The types that are manipulated are Query for which an IsString instance exists. Don't force yourself to use the higher-level API if the lower-level combinators work for you, and if those don't either, “Just Write SQL”™.

Its dependency footprint is optimised for my own setups, and as such it makes use of text, vector and pg-transact.

Table of Contents

Installation

At present time, pg-entity is published on Hackage but not on Stackage. To use it in your projects, add it in your cabal file like this:

pg-entity ^>= 0.0

or in your stack.yaml file:

extra-deps:
- pg-entity-0.0.1.0

The following GHC versions are supported:

  • 8.8
  • 8.10
  • 9.0

Documentation

This library aims to be thoroughly tested, by the means of Oleg Grerus' cabal-docspec and more traditional tests for database roundtrips.

I aim to produce and maintain a decent documentation, therefore do not hesitate to raise an issue if you feel that something is badly explained and should be improved.

You will find the Tutorial here, and you will find below a short showcase of the library.

Usage

The idea is to implement the Entity typeclass for the datatypes that represent your PostgreSQL table.

-- Traditional list & string syntax
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
-- Quasi-quoter to construct SQL expressions
{-# LANGUAGE QuasiQuotes #-}
-- Deriving machinery
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingVia #-}
importData.UUID (UUID)
importData.Vector (Vector)
importDatabase.PostgreSQL.Simple.SqlQQimportDatabase.PostgreSQL.Entity-- This is our Primary Key newtype. It is wrapped in a newtype to make-- it impossible to mitake with a plain `UUID`, but we still want to-- benefit from the pre-existing typeclass instances that exist for-- `UUID`. You can read the last two lines as:-- > We use the definitions posessed by `UUID` for our own newtype.newtypeJobId=JobId{getJobId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID-- A straightforward table definition, which lets us use-- the DerivingVia mechanism to declare the table name-- in the `deriving` clause, and infer the fields and primary key.-- The field names will be converted to snake_case.dataJob=Job{jobId::JobId
, lockedAt::UTCTime
, jobName::Text}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
derivingEntityvia (GenericEntity '[TableName"jobs"] Job)
-- In the above deriving clause, we only had to specify the table name in order to pluralise it,-- leaving the guessing of the primary key and the table names to the library.-- Below is a richer table definition that needs some type annotations to help PostgreSQL.-- We will have to write out the full instance by handnewtypeBagId=BagId{getBagId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID--| This is a PostgreSQL Enum, which needs to be marked as such in SQL type annotations.dataProperties=P1 | P2 | P3derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
dataBag=Bag{bagId::BagId
, someField::VectorUUID
, properties::VectorProperties}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
instanceEntityBagwhere
tableName ="bags"
primaryKey = [field| bag_id |]
fields = [ [field| bag_id |]
, [field| some_field :: uuid[] |]
, [field| properties :: properties[] |]
]
-- You can write specialised functions to remove the noise of Type ApplicationsinsertBag::Bag->DBTIO()
insertBag = insert -- `insert` will be specialised to `Bag`-- And you can insert raw SQL through postgresql-simpleisJobLocked::Int->DBTIO (OnlyBool)
isJobLocked jobId = queryOne Select q (Only jobId)
where q = [sql| SELECT
CASE WHEN locked_at IS NULL then false
ELSE true
END
FROM jobs WHERE job_id = ?
|]

For more examples, see the BlogPost module for the data-type that is used throughout the tests and doctests.

Escape hatches

Safe SQL generation is a complex subject, and it is far from being the objective of this library. The main topic it addresses is listing the fields of a table, which is definitely something easier. This is why every level of this wrapper is fully exposed, so that you can drop down a level at your convience.

It is my personal belief, firmly rooted in experience, that we should not aim to produce statically-checked SQL and have it "verified" by the compiler. The techniques that would allow that in Haskell are still far from being optimised and ergonomic. As such, this library makes no effort to produce semantically valid SQL queries, because one would have to encode the semantics of SQL in the type system (or in a rule engine of some sort), and this is clearly not the kind of things I want to spend my youth on.

Each function is tested for its output with doctests, and the ones that cannot (due to database connections) are tested in the more traditional test-suite.

The conclusion is : Test your DB queries. Test the encoding/decoding. Make roundtrip tests for your data-structures.

Acknowledgements

I wish to thank

  • Clément Delafargue, whose anorm-pg-entity library and its initial port in Haskell are the spiritual parents of this library
  • Koz Ross, for his piercing eyes and his immense patience
  • Joe Kachmar, who enlightened me many times

About

A pleasant PostgreSQL database layer for Haskell

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

PG-Entity CI-badgedocssimple-haskell

This library is a pleasant layer on top of postgresql-simple to safely expand the fields of a table when writing SQL queries.
It aims to be a convenient middle-ground between rigid ORMs and hand-rolled SQL query strings. Here is its philosophy:

  • The serialisation/deserialisation part is left to the consumer, so you have to go with your own FromRow/ToRow instances. You are encouraged to adopt data types that model your business, rather than restrict yourself within the limits of what an SQL schema can represent. Use an intermediate Data Access Object (DAO) that can easily be serialised and deserialised to and from a SQL schema, to and from which you will morph your business data-types.
  • Illegal states are made harder (but not impossible) to represent. Generic deriving of entities is encouraged, and quasi-quoters are provided to denote fields in a safer way.
  • Escape hatches are provided at every level. The types that are manipulated are Query for which an IsString instance exists. Don't force yourself to use the higher-level API if the lower-level combinators work for you, and if those don't either, “Just Write SQL”™.

Its dependency footprint is optimised for my own setups, and as such it makes use of text, vector and pg-transact.

Table of Contents

Installation

At present time, pg-entity is published on Hackage but not on Stackage. To use it in your projects, add it in your cabal file like this:

pg-entity ^>= 0.0

or in your stack.yaml file:

extra-deps:
- pg-entity-0.0.1.0

The following GHC versions are supported:

  • 8.8
  • 8.10
  • 9.0

Documentation

This library aims to be thoroughly tested, by the means of Oleg Grerus' cabal-docspec and more traditional tests for database roundtrips.

I aim to produce and maintain a decent documentation, therefore do not hesitate to raise an issue if you feel that something is badly explained and should be improved.

You will find the Tutorial here, and you will find below a short showcase of the library.

Usage

The idea is to implement the Entity typeclass for the datatypes that represent your PostgreSQL table.

-- Traditional list & string syntax
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
-- Quasi-quoter to construct SQL expressions
{-# LANGUAGE QuasiQuotes #-}
-- Deriving machinery
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingVia #-}
importData.UUID (UUID)
importData.Vector (Vector)
importDatabase.PostgreSQL.Simple.SqlQQimportDatabase.PostgreSQL.Entity-- This is our Primary Key newtype. It is wrapped in a newtype to make-- it impossible to mitake with a plain `UUID`, but we still want to-- benefit from the pre-existing typeclass instances that exist for-- `UUID`. You can read the last two lines as:-- > We use the definitions posessed by `UUID` for our own newtype.newtypeJobId=JobId{getJobId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID-- A straightforward table definition, which lets us use-- the DerivingVia mechanism to declare the table name-- in the `deriving` clause, and infer the fields and primary key.-- The field names will be converted to snake_case.dataJob=Job{jobId::JobId
, lockedAt::UTCTime
, jobName::Text}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
derivingEntityvia (GenericEntity '[TableName"jobs"] Job)
-- In the above deriving clause, we only had to specify the table name in order to pluralise it,-- leaving the guessing of the primary key and the table names to the library.-- Below is a richer table definition that needs some type annotations to help PostgreSQL.-- We will have to write out the full instance by handnewtypeBagId=BagId{getBagId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID--| This is a PostgreSQL Enum, which needs to be marked as such in SQL type annotations.dataProperties=P1 | P2 | P3derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
dataBag=Bag{bagId::BagId
, someField::VectorUUID
, properties::VectorProperties}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
instanceEntityBagwhere
tableName ="bags"
primaryKey = [field| bag_id |]
fields = [ [field| bag_id |]
, [field| some_field :: uuid[] |]
, [field| properties :: properties[] |]
]
-- You can write specialised functions to remove the noise of Type ApplicationsinsertBag::Bag->DBTIO()
insertBag = insert -- `insert` will be specialised to `Bag`-- And you can insert raw SQL through postgresql-simpleisJobLocked::Int->DBTIO (OnlyBool)
isJobLocked jobId = queryOne Select q (Only jobId)
where q = [sql| SELECT
CASE WHEN locked_at IS NULL then false
ELSE true
END
FROM jobs WHERE job_id = ?
|]

For more examples, see the BlogPost module for the data-type that is used throughout the tests and doctests.

Escape hatches

Safe SQL generation is a complex subject, and it is far from being the objective of this library. The main topic it addresses is listing the fields of a table, which is definitely something easier. This is why every level of this wrapper is fully exposed, so that you can drop down a level at your convience.

It is my personal belief, firmly rooted in experience, that we should not aim to produce statically-checked SQL and have it "verified" by the compiler. The techniques that would allow that in Haskell are still far from being optimised and ergonomic. As such, this library makes no effort to produce semantically valid SQL queries, because one would have to encode the semantics of SQL in the type system (or in a rule engine of some sort), and this is clearly not the kind of things I want to spend my youth on.

Each function is tested for its output with doctests, and the ones that cannot (due to database connections) are tested in the more traditional test-suite.

The conclusion is : Test your DB queries. Test the encoding/decoding. Make roundtrip tests for your data-structures.

Acknowledgements

I wish to thank

  • Clément Delafargue, whose anorm-pg-entity library and its initial port in Haskell are the spiritual parents of this library
  • Koz Ross, for his piercing eyes and his immense patience
  • Joe Kachmar, who enlightened me many times

About

A pleasant PostgreSQL database layer for Haskell

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

PG-Entity CI-badgedocssimple-haskell

This library is a pleasant layer on top of postgresql-simple to safely expand the fields of a table when writing SQL queries.
It aims to be a convenient middle-ground between rigid ORMs and hand-rolled SQL query strings. Here is its philosophy:

  • The serialisation/deserialisation part is left to the consumer, so you have to go with your own FromRow/ToRow instances. You are encouraged to adopt data types that model your business, rather than restrict yourself within the limits of what an SQL schema can represent. Use an intermediate Data Access Object (DAO) that can easily be serialised and deserialised to and from a SQL schema, to and from which you will morph your business data-types.
  • Illegal states are made harder (but not impossible) to represent. Generic deriving of entities is encouraged, and quasi-quoters are provided to denote fields in a safer way.
  • Escape hatches are provided at every level. The types that are manipulated are Query for which an IsString instance exists. Don't force yourself to use the higher-level API if the lower-level combinators work for you, and if those don't either, “Just Write SQL”™.

Its dependency footprint is optimised for my own setups, and as such it makes use of text, vector and pg-transact.

Table of Contents

Installation

At present time, pg-entity is published on Hackage but not on Stackage. To use it in your projects, add it in your cabal file like this:

pg-entity ^>= 0.0

or in your stack.yaml file:

extra-deps:
- pg-entity-0.0.1.0

The following GHC versions are supported:

  • 8.8
  • 8.10
  • 9.0

Documentation

This library aims to be thoroughly tested, by the means of Oleg Grerus' cabal-docspec and more traditional tests for database roundtrips.

I aim to produce and maintain a decent documentation, therefore do not hesitate to raise an issue if you feel that something is badly explained and should be improved.

You will find the Tutorial here, and you will find below a short showcase of the library.

Usage

The idea is to implement the Entity typeclass for the datatypes that represent your PostgreSQL table.

-- Traditional list & string syntax
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
-- Quasi-quoter to construct SQL expressions
{-# LANGUAGE QuasiQuotes #-}
-- Deriving machinery
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingVia #-}
importData.UUID (UUID)
importData.Vector (Vector)
importDatabase.PostgreSQL.Simple.SqlQQimportDatabase.PostgreSQL.Entity-- This is our Primary Key newtype. It is wrapped in a newtype to make-- it impossible to mitake with a plain `UUID`, but we still want to-- benefit from the pre-existing typeclass instances that exist for-- `UUID`. You can read the last two lines as:-- > We use the definitions posessed by `UUID` for our own newtype.newtypeJobId=JobId{getJobId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID-- A straightforward table definition, which lets us use-- the DerivingVia mechanism to declare the table name-- in the `deriving` clause, and infer the fields and primary key.-- The field names will be converted to snake_case.dataJob=Job{jobId::JobId
, lockedAt::UTCTime
, jobName::Text}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
derivingEntityvia (GenericEntity '[TableName"jobs"] Job)
-- In the above deriving clause, we only had to specify the table name in order to pluralise it,-- leaving the guessing of the primary key and the table names to the library.-- Below is a richer table definition that needs some type annotations to help PostgreSQL.-- We will have to write out the full instance by handnewtypeBagId=BagId{getBagId::UUID}deriving (Eq, Show, FromField, ToField)
viaUUID--| This is a PostgreSQL Enum, which needs to be marked as such in SQL type annotations.dataProperties=P1 | P2 | P3derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
dataBag=Bag{bagId::BagId
, someField::VectorUUID
, properties::VectorProperties}derivingstock (Eq, Generic, Show)
derivinganyclass (FromRow, ToRow)
instanceEntityBagwhere
tableName ="bags"
primaryKey = [field| bag_id |]
fields = [ [field| bag_id |]
, [field| some_field :: uuid[] |]
, [field| properties :: properties[] |]
]
-- You can write specialised functions to remove the noise of Type ApplicationsinsertBag::Bag->DBTIO()
insertBag = insert -- `insert` will be specialised to `Bag`-- And you can insert raw SQL through postgresql-simpleisJobLocked::Int->DBTIO (OnlyBool)
isJobLocked jobId = queryOne Select q (Only jobId)
where q = [sql| SELECT
CASE WHEN locked_at IS NULL then false
ELSE true
END
FROM jobs WHERE job_id = ?
|]

For more examples, see the BlogPost module for the data-type that is used throughout the tests and doctests.

Escape hatches

Safe SQL generation is a complex subject, and it is far from being the objective of this library. The main topic it addresses is listing the fields of a table, which is definitely something easier. This is why every level of this wrapper is fully exposed, so that you can drop down a level at your convience.

It is my personal belief, firmly rooted in experience, that we should not aim to produce statically-checked SQL and have it "verified" by the compiler. The techniques that would allow that in Haskell are still far from being optimised and ergonomic. As such, this library makes no effort to produce semantically valid SQL queries, because one would have to encode the semantics of SQL in the type system (or in a rule engine of some sort), and this is clearly not the kind of things I want to spend my youth on.

Each function is tested for its output with doctests, and the ones that cannot (due to database connections) are tested in the more traditional test-suite.

The conclusion is : Test your DB queries. Test the encoding/decoding. Make roundtrip tests for your data-structures.

Acknowledgements

I wish to thank

  • Clément Delafargue, whose anorm-pg-entity library and its initial port in Haskell are the spiritual parents of this library
  • Koz Ross, for his piercing eyes and his immense patience
  • Joe Kachmar, who enlightened me many times

About

A pleasant PostgreSQL database layer for Haskell

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages