2023-10-27 18:37:10 +03:00
|
|
|
<img
|
|
|
|
src="images/orville-waving-pennant.svg"
|
|
|
|
width="250px"
|
|
|
|
height="250px"
|
|
|
|
align="left">
|
|
|
|
|
|
|
|
# Orville - a Haskell library for PostgreSQL
|
|
|
|
|
|
|
|
Orville's goal is to provide a powerful API for applications to access
|
|
|
|
PostgreSQL databases with minimal use of sophisticated language techniques or
|
|
|
|
extensions. It strikes a balance between enforcing type-safety in database
|
|
|
|
interactions where it is reasonable and presenting type signatures that are
|
|
|
|
minimally complicated.
|
|
|
|
|
|
|
|
<br clear="left"/>
|
|
|
|
|
|
|
|
## Why Orville?
|
|
|
|
|
|
|
|
Orville is not meant to replace existing PostgreSQL libraries in the Haskell
|
2023-10-27 19:51:17 +03:00
|
|
|
ecosystem, but to complement them. It has the power to satisfy most experienced
|
2023-10-27 18:37:10 +03:00
|
|
|
Haskell developers but strives to remain approachable to newcomers despite
|
|
|
|
this. Orville's API is rich enough to be used in production on large and
|
|
|
|
sophisticated applications, but avoids complicated type-level programming. If
|
|
|
|
your application is too large to reasonably write all your SQL statements by
|
|
|
|
hand yet doesn't require absolute type-safety between your custom SQL
|
2023-10-27 19:51:17 +03:00
|
|
|
statements, their result sets and the Haskell types they decode into, Orville
|
|
|
|
may be the right choice for you.
|
2023-10-27 18:37:10 +03:00
|
|
|
|
|
|
|
## Feature Overview
|
|
|
|
|
|
|
|
* Rich API for marshalling Haskell types to and from SQL
|
|
|
|
* High-level APIs for common CRUD operations
|
|
|
|
* Optional automatic schema migrations
|
|
|
|
* Optional API for executing complex data loads across multiple tables without ever writing an N+1 query by accident
|
|
|
|
* Progressive escape hatches to let you dig deeper when you need to
|
|
|
|
|
|
|
|
## Tutorials
|
2023-02-24 00:31:58 +03:00
|
|
|
|
|
|
|
See the tutorials, in order of increasing complexity:
|
|
|
|
|
2023-11-18 01:40:34 +03:00
|
|
|
* [Getting Started](GETTING-STARTED.md)
|
|
|
|
* [Using SqlMarshaller](SQL-MARSHALLER.md)
|
|
|
|
* [Using Plans](PLAN.md)
|
|
|
|
* [Using Migrations](MIGRATION.md)
|
|
|
|
* [Using JSON](JSON.md)
|
2023-02-24 00:31:58 +03:00
|
|
|
|
|
|
|
Additional documentation is available in the Haddocks.
|
2023-10-27 18:37:10 +03:00
|
|
|
|
|
|
|
## Just show me some code!
|
|
|
|
|
2023-10-27 19:51:17 +03:00
|
|
|
Ok! Here's a very simple application that inserts some entities of a `Pet`
|
|
|
|
model and finds one of them based on its name.
|
2023-10-27 18:37:10 +03:00
|
|
|
|
|
|
|
```haskell
|
|
|
|
module Main (main) where
|
|
|
|
|
|
|
|
import Data.Int (Int32)
|
|
|
|
import qualified Data.Text as T
|
|
|
|
import qualified Orville.PostgreSQL as O
|
|
|
|
import qualified Orville.PostgreSQL.AutoMigration as AutoMigration
|
|
|
|
|
|
|
|
{- |
|
|
|
|
Pet is a plain old Haskell record that will be marshalled to and from the
|
|
|
|
@pet@ table.
|
|
|
|
-}
|
|
|
|
data Pet =
|
|
|
|
Pet
|
|
|
|
{ petId :: PetId
|
|
|
|
, petName :: T.Text
|
|
|
|
}
|
|
|
|
|
|
|
|
{- |
|
2023-10-27 20:44:45 +03:00
|
|
|
It's good practice to create newtype specific to each entity to hold its
|
|
|
|
primary key value
|
2023-10-27 18:37:10 +03:00
|
|
|
-}
|
|
|
|
newtype PetId = PetId Int32
|
|
|
|
|
|
|
|
{- |
|
|
|
|
A marshaller must be defined to convert Pet to and from SQL.
|
|
|
|
-}
|
|
|
|
petMarshaller :: O.SqlMarshaller Pet Pet
|
|
|
|
petMarshaller =
|
|
|
|
Pet
|
|
|
|
<$> O.marshallField petId petIdField
|
|
|
|
<*> O.marshallField petName nameField
|
|
|
|
|
|
|
|
{- |
|
|
|
|
Defines the @id@ field for the marshaller to marshall the 'petId' record
|
|
|
|
field to and from.
|
|
|
|
-}
|
|
|
|
petIdField :: O.FieldDefinition O.NotNull PetId
|
|
|
|
petIdField =
|
|
|
|
O.coerceField (O.integerField "id")
|
|
|
|
|
|
|
|
{- |
|
|
|
|
Defines the @name@ field for the marshaller to marshall the 'petName' record
|
|
|
|
field to and from.
|
|
|
|
-}
|
|
|
|
nameField :: O.FieldDefinition O.NotNull T.Text
|
|
|
|
nameField =
|
|
|
|
O.unboundedTextField "name"
|
|
|
|
|
|
|
|
{- |
|
2023-10-27 20:44:45 +03:00
|
|
|
Marshaller above is associated with the @pet@ table. The marshallers fields
|
|
|
|
will define the column of the table.
|
2023-10-27 18:37:10 +03:00
|
|
|
-}
|
|
|
|
petTable :: O.TableDefinition (O.HasKey PetId) Pet Pet
|
|
|
|
petTable =
|
|
|
|
O.mkTableDefinition
|
|
|
|
"pet"
|
|
|
|
(O.primaryKey petIdField)
|
|
|
|
petMarshaller
|
|
|
|
|
|
|
|
{- |
|
|
|
|
A simple demo that connects to a database, inserts 2 pets and then finds the
|
|
|
|
pet named "Spot"
|
|
|
|
-}
|
|
|
|
main :: IO ()
|
|
|
|
main = do
|
|
|
|
pool <-
|
|
|
|
O.createConnectionPool
|
|
|
|
O.ConnectionOptions
|
|
|
|
{ O.connectionString = "host=localhost user=orville password=orville"
|
|
|
|
, O.connectionNoticeReporting = O.DisableNoticeReporting
|
|
|
|
, O.connectionPoolStripes = O.OneStripePerCapability
|
2023-10-27 20:44:45 +03:00
|
|
|
, O.connectionPoolMaxConnections = O.MaxConnectionsPerStripe 1
|
2023-10-27 18:37:10 +03:00
|
|
|
, O.connectionPoolLingerTime = 10
|
|
|
|
}
|
|
|
|
|
|
|
|
mbSpot <- O.runOrville pool insertAndFindSpot
|
|
|
|
|
|
|
|
case mbSpot of
|
|
|
|
Nothing -> putStrLn "No Spot Found!"
|
|
|
|
Just _spot -> putStrLn "Spot found!"
|
|
|
|
|
|
|
|
{- |
|
|
|
|
The Orville monad provides a starter pack for running Orville operations
|
|
|
|
against a connection pool.
|
|
|
|
-}
|
|
|
|
insertAndFindSpot :: O.Orville (Maybe Pet)
|
|
|
|
insertAndFindSpot = do
|
|
|
|
AutoMigration.autoMigrateSchema
|
|
|
|
AutoMigration.defaultOptions
|
|
|
|
[AutoMigration.SchemaTable petTable]
|
|
|
|
|
|
|
|
O.insertEntity petTable $
|
|
|
|
Pet
|
|
|
|
{ petId = PetId 1
|
|
|
|
, petName = T.pack "FuFu"
|
|
|
|
}
|
|
|
|
|
|
|
|
O.insertEntity petTable $
|
|
|
|
Pet
|
|
|
|
{ petId = PetId 2
|
|
|
|
, petName = T.pack "Spot"
|
|
|
|
}
|
|
|
|
|
|
|
|
O.findFirstEntityBy
|
|
|
|
petTable
|
|
|
|
(O.where_ (O.fieldEquals nameField (T.pack "Spot")))
|
|
|
|
```
|