A helper function to tell GHC what the EntityWithPrefix prefix
should be. This allows you to use a type application to specify the
prefix, instead of specifying the etype on the result.
As an example, here's code that uses this:
myQuery :: SqlPersistM [Entity Person]
myQuery = fmap (unPrefix @"p") $ rawSql query []
where
query = "SELECT ?? FROM person AS p"
This newtype wrapper is useful when selecting an entity out of the
database and you want to provide a prefix to the table being selected.
Consider this raw SQL query:
SELECT ??
FROM my_long_table_name AS mltn
INNER JOIN other_table AS ot
ON mltn.some_col = ot.other_col
WHERE ...
We don't want to refer to my_long_table_name every time, so we create
an alias. If we want to select it, we have to tell the raw SQL
quasi-quoter that we expect the entity to be prefixed with some other
name.
We can give the above query a type with this, like:
If your database supports non-standard types, such as Postgres' uuid, you can use SqlOther to use them:
import qualified Data.UUID as UUID
instance PersistField UUID where
toPersistValue = PersistLiteralEncoded . toASCIIBytes
fromPersistValue (PersistLiteralEncoded uuid) =
case fromASCIIBytes uuid of
Nothing -> Left $ "Model/CustomTypes.hs: Failed to deserialize a UUID; received: " <> T.pack (show uuid)
Just uuid' -> Right uuid'
fromPersistValue x = Left $ "File.hs: When trying to deserialize a UUID: expected PersistLiteralEncoded, received: "-- > <> T.pack (show x)
instance PersistFieldSql UUID where
sqlType _ = SqlOther "uuid"
User Created Database Types
Similarly, some databases support creating custom types, e.g. Postgres' DOMAIN and ENUM features. You can use SqlOther to specify a custom type:
CREATE DOMAIN ssn AS text
CHECK ( value ~ '^[0-9]{9}$');
instance PersistFieldSQL SSN where
sqlType _ = SqlOther "ssn"
CREATE TYPE rainbow_color AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet');
instance PersistFieldSQL RainbowColor where
sqlType _ = SqlOther "rainbow_color"
This type uses the SqlInt64 version, which will exhibit overflow and
underflow behavior. Additionally, it permits negative values in the
database, which isn't ideal.
Starts a new transaction on the connection. When the acquired connection
is released the transaction is committed and the connection returned to the
pool.
Upon an exception the transaction is rolled back and the connection
destroyed.
This is equivalent to runSqlConn but does not incur the MonadUnliftIO
constraint, meaning it can be used within, for example, a Conduit
pipeline.
Get a connection from the pool, run the given action, and then return the
connection to the pool.
This function performs the given action in a transaction. If an
exception occurs during the action, then the transaction is rolled back.
Note: This function previously timed out after 2 seconds, but this behavior
was buggy and caused more problems than it solved. Since version 2.1.2, it
performs no timeout checks.
This action is performed when an exception is received. The
exception is provided as a convenience - it is rethrown once this
cleanup function is complete.
This function is how runSqlPool and runSqlPoolNoTransaction are
defined. In addition to the action to be performed and the Pool of
conections to use, we give you the opportunity to provide three actions
- initialize, afterwards, and onException.
Creates a pool of connections to a SQL database which can be used by the Pool backend -> m a function.
After the function completes, the connections are destroyed.
Defined in persistent-2.18.1.0 · Database.Persist.Compatible.Types
The Escape Hatch
5 declarations
persistent offers a set of functions that are useful for operating
directly on the underlying SQL database. This can allow you to use
whatever SQL features you want.
Execute a raw SQL statement and return its results as a
list. If you do not expect a return value, use of
rawExecute is recommended.
If you're using Entitys (which is quite likely), then you
must use entity selection placeholders (double question
mark, ??). These ?? placeholders are then replaced for
the names of the columns that we need for your entities.
You'll receive an error if you don't use the placeholders.
Please see the Entitys documentation for more details.
You may put value placeholders (question marks, ?) in your
SQL query. These placeholders are then replaced by the values
you pass on the second parameter, already correctly escaped.
You may want to use toPersistValue to help you constructing
the placeholder values.
Since you're giving a raw SQL statement, you don't get any
guarantees regarding safety. If rawSql is not able to parse
the results of your query back, then an exception is raised.
However, most common problems are mitigated by using the
entity selection placeholder ??, and you shouldn't see any
error at all if you're not using Single.
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
name String
age Int Maybe
deriving Show
BlogPost
title String
authorId PersonId
deriving Show
|]
Examples based on the above schema:
getPerson :: MonadIO m => ReaderT SqlBackend m [Entity Person]
getPerson = rawSql "select ?? from person where name=?" [PersistText "john"]
getAge :: MonadIO m => ReaderT SqlBackend m [Single Int]
getAge = rawSql "select person.age from person where name=?" [PersistText "john"]
getAgeName :: MonadIO m => ReaderT SqlBackend m [(Single Int, Single Text)]
getAgeName = rawSql "select person.age, person.name from person where name=?" [PersistText "john"]
getPersonBlog :: MonadIO m => ReaderT SqlBackend m [(Entity Person, Entity BlogPost)]
getPersonBlog = rawSql "select ??,?? from person,blog_post where person.id = blog_post.author_id" []
Minimal working program for PostgreSQL backend based on the above concepts:
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Logger (runStderrLoggingT)
import Database.Persist
import Control.Monad.Reader
import Data.Text
import Database.Persist.Sql
import Database.Persist.Postgresql
import Database.Persist.TH
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
name String
age Int Maybe
deriving Show
|]
conn = "host=localhost dbname=new_db user=postgres password=postgres port=5432"
getPerson :: MonadIO m => ReaderT SqlBackend m [Entity Person]
getPerson = rawSql "select ?? from person where name=?" [PersistText "sibi"]
liftSqlPersistMPool y x = liftIO (runSqlPersistMPool y x)
main :: IO ()
main = runStderrLoggingT $ withPostgresqlPool conn 10 $ liftSqlPersistMPool $ do
runMigration migrateAll
xs <- getPerson
liftIO (print xs)
Render a [Filter record] into a Text value suitable for inclusion
into a SQL query, as well as the [PersistValue] to properly fill in the
? place holders.
Prefix the column name with the EXCLUDED keyword. This is used with
the Postgresql backend when doing ON CONFLICT DO UPDATE clauses - see
the documentation on upsertWhere and upsertManyWhere.
Commit the current transaction and begin a new one.
This is used when a transaction commit is required within the context of runSqlConn
(which brackets its provided action with a transaction begin/commit pair).
Roll back the current transaction and begin a new one.
This rolls back to the state of the last call to transactionSave or the enclosing
runSqlConn call.
Record of functions to override the default behavior in mkColumns. It is
recommended you initialize this with emptyBackendSpecificOverrides and
override the default values, so that as new fields are added, your code still
compiles.
For added safety, use the getBackendSpecific* and setBackendSpecific*
functions, as a breaking change to the record field labels won't be reflected
in a major version bump of the library.
If the override is defined, then this returns a function that accepts an
entity name and field name and provides the ConstraintNameDB for the
foreign key constraint.
Prior to persistent-2.11.0, we provided an instance of
PersistField for the Natural type. This was in error, because
Natural represents an infinite value, and databases don't have
reasonable types for this.
The instance for Natural used the Int64 underlying type, which will
cause underflow and overflow errors. This type has the exact same code
in the instances, and will work seamlessly.
A more appropriate type for this is the Word series of types from
Data.Word. These have a bounded size, are guaranteed to be
non-negative, and are quite efficient for the database to store.
This type uses the SqlInt64 version, which will exhibit overflow and
underflow behavior. Additionally, it permits negative values in the
database, which isn't ideal.
A SqlBackend represents a handle or connection to a database. It
contains functions and values that allow databases to have more
optimized implementations, as well as references that benefit
performance and sharing.
A SqlBackend is *not* thread-safe. You should not assume that
a SqlBackend can be shared among threads and run concurrent queries.
This *will* result in problems. Instead, you should create a PoolSqlBackend, known as a ConnectionPool, and pass that around in
multi-threaded applications.
To run actions in the persistent library, you should use the
runSqlConn function. If you're using a multithreaded application, use
the runSqlPool function.