purescript-postgresql-client/README.md

167 lines
6.5 KiB
Markdown
Raw Permalink Normal View History

2016-12-24 12:34:27 +00:00
# purescript-postgresql-client
2019-11-10 14:27:24 +00:00
purescript-postgresql-client is a PostgreSQL client library for PureScript based on `node-postgres`.
2016-12-25 22:58:29 +00:00
## Install
2018-10-22 01:27:35 +00:00
To use this library, you need to add [`pg`][pg] and [`decimal.js`][decimal.js] as an npm dependency. You can also
find first of them on [https://github.com/brianc/node-postgres](https://github.com/brianc/node-postgres).
## Usage
2018-10-21 01:58:45 +00:00
This guide is a literate Purescript file which is extracted into testing module (using [`literate-purescript`](https://github.com/Thimoteus/literate-purescript)) so it is a little verbose.
2018-10-21 09:39:25 +00:00
Let's start with imports.
```purescript
2018-12-06 19:14:08 +00:00
module Test.README where
import Prelude
import Control.Monad.Except.Trans (ExceptT, runExceptT)
2020-11-10 16:47:16 +00:00
import Database.PostgreSQL (Connection, fromPool, Pool, Query(Query), PGError)
import Database.PostgreSQL.PG (command, execute, query, withTransaction) as PG
import Database.PostgreSQL.Pool (new) as Pool
import Database.PostgreSQL.Row (Row0(Row0), Row3(Row3))
import Data.Decimal as Decimal
import Data.Tuple.Nested ((/\))
import Effect.Aff (Aff)
import Effect.Aff.Class (liftAff)
2018-10-21 01:56:11 +00:00
import Effect.Class (liftEffect)
import Test.Assert (assert)
import Test.Config (load) as Config
2018-10-21 09:39:25 +00:00
```
2018-12-06 19:30:38 +00:00
The whole API for interaction with PostgreSQL is performed asynchronously in `Aff`
(the only function which runs in plain `Effect` is `Pool.new`). Core library
2019-05-07 18:16:59 +00:00
functions usually results in somthing like `Aff (Either PGError a)` which can be easily
wrapped by user into `ExceptT` or any other custom monad stack. This base API is exposed by
`PostgreSQL.Aff` module.
To be honest we provide alternatives to functions in the `Database.PostgreSQL.PG` module that work on any stack `m` with `MonadError PGError m` and `MonadAff m`.
The module contains two functions `withClient` and `withTransaction` that require additional parameter - a transformation from a custom monad stack to `Aff (Either PGError a)`.
We are going to work with custom `AppM` type in this tutorial but please don't consider it as the only option
2019-05-07 18:16:59 +00:00
if you encounter any troubles integrating it into your own app monad stack.
2018-12-06 19:30:38 +00:00
```purescript
type AppM a = ExceptT PGError Aff a
withTransaction :: forall a. Pool -> (Connection -> AppM a) -> AppM a
withTransaction p = PG.withTransaction runExceptT p
```
2018-12-06 19:30:38 +00:00
Our tests runner reads the configuration for the current process environment or from
the _.env_ file (please check _.env-example_ for details).
2019-05-07 18:16:59 +00:00
We assume here that Postgres is running on a standard local port
with `ident` authentication so configuration can be nearly empty (`defaultConfiguration`).
2021-11-08 16:48:42 +00:00
It requires only database name which we pass to the `newPool` function.
2019-05-07 18:16:59 +00:00
Additionally we pass `idleTimeoutMillis` value because this code
2018-12-09 20:50:13 +00:00
is run by our test suite and we want to exit after its execution quickly ;-)
2018-10-21 09:39:25 +00:00
2018-10-21 09:39:25 +00:00
```purescript
run ∷ AppM Unit
run = do
config ← liftAff $ Config.load
pool ← liftEffect $ Pool.new config
2018-10-21 09:39:25 +00:00
```
We can now create our temporary table which we are going to query in this example.
`PG.execute` ignores result value which is what we want in this case.
2018-10-21 09:41:25 +00:00
The last `Row0` value indicates that this `Query` doesn't take any additional parameters.
2018-10-21 09:39:25 +00:00
2020-11-10 16:47:16 +00:00
Database quering functions like `execute` below can perform the action using the pool
or the connection instance so they expect a value of type `Connection` (which is just
a wrapper around `Either` - `newtype Connection = Connection (Either Pool Client)`).
2018-10-21 09:39:25 +00:00
```purescript
2020-11-10 16:47:16 +00:00
PG.execute (fromPool pool) (Query """
CREATE TEMPORARY TABLE fruits (
name text NOT NULL,
delicious boolean NOT NULL,
price NUMERIC(4,2) NOT NULL,
added TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (name)
);
""") Row0
2018-10-21 09:39:25 +00:00
```
There is a `withTransaction` helper provided. You can wrap the whole
piece of interaction with database in it. It will rollback if any exception
is thrown during execution of a given `Aff` block. It excecutes `COMMIT`
in the other case.
We start our session with insert of some data. It is done by `PG.execute`
function with `INSERT` statement.
Please notice that we are passing a tuple of the arguments to this query
using dedicated constructor. In this case `Row3`. This library provides types
from `Row0` to `Row19` and they are wrappers which provide instances for
automatic conversions from and to SQL values.
2019-05-07 18:16:59 +00:00
For details please investigate following classes `ToSQLRow`, `ToSQLValue`,
`FromSQLRow` and `FromSQLValue`.
```purescript
withTransaction pool \h -> do
PG.execute h (Query """
INSERT INTO fruits (name, delicious, price)
VALUES ($1, $2, $3)
""") (Row3 "coconut" true (Decimal.fromString "8.30"))
2018-10-21 09:39:25 +00:00
```
2018-10-21 09:39:25 +00:00
We can also use nested tuples instead of `Row*` constructors. This can be a bit more
verbose but is not restricted to limited and constant number of arguments.
`/\` is just an alias for the `Tuple` constructor from `Data.Tuple.Nested`.
2018-10-21 09:39:25 +00:00
```purescript
PG.execute h (Query """
INSERT INTO fruits (name, delicious, price)
VALUES ($1, $2, $3)
""") ("lemon" /\ false /\ Decimal.fromString "3.30")
2018-10-21 09:39:25 +00:00
```
Of course `Row*` types and nested tuples can be also used when we are fetching
data from db.
`query` function processes db response and returns an `Array` of rows.
```purescript
2020-11-10 16:47:16 +00:00
names <- PG.query (fromPool pool) (Query """
SELECT name, delicious
FROM fruits
ORDER BY name ASC
""") Row0
liftEffect <<< assert $ names == ["coconut" /\ true, "lemon" /\ false]
2018-10-21 09:39:25 +00:00
```
There is also a `command` function at our disposal.
Some postgres SQL expressions return a "command tag" which carries
a value with a number of rows which were affected by a given query.
2018-10-21 09:39:25 +00:00
For example we can have: `DELETE rows`, `UPDATE rows`, `INSERT oid rows` etc.
This function should return `rows` value associated with given response.
2018-10-21 09:39:25 +00:00
```purescript
2020-11-10 16:47:16 +00:00
deleted <- PG.command (fromPool pool) (Query """DELETE FROM fruits """) Row0
liftEffect <<< assert $ deleted == 2
```
2020-02-29 15:44:54 +00:00
## Generating SQL Queries
The `purspg` preprocessor has been replaced by `sqltopurs`, which is a code
generator instead of a preprocessor, and easier to use.
[sqltopurs]: https://github.com/rightfold/sqltopurs
[pg]: https://www.npmjs.com/package/pg
[decimal.js]: https://www.npmjs.com/package/decimal.js
## Hacking
### Testing
2018-10-21 01:32:06 +00:00
Test database is read from the environment or loaded from _.env_ file. You can find _.env-example_ in the repo with some simple testing db setup.
2020-02-29 15:44:54 +00:00
### Releasing
2020-02-29 15:48:02 +00:00
Till we are hosted on the github platform let's just use github releasing model for tagging new versions and `github-release-notes` to generate CHANGELOG.md from it:
`$ # This only requires repo access`
`$ export GREN_GITHUB_TOKEN=...`
`$ github-release-notes changelog --override`