This module contains a new way (introduced in 3.3.3.0) of using FROM in
Haskell. The old method was a bit finicky and could permit runtime errors,
and this new way is both significantly safer and much more powerful.
This syntax will become the default syntax exported from the library in
version 3.6.0.0. To use the old syntax, see Database.Esqueleto.Legacy.
If you're already using Database.Esqueleto, then you can get
started using this module just by changing your imports slightly,
as well as enabling the TypeApplications extension.
{-# LANGUAGE TypeApplications #-}
...
import Database.Esqueleto.Experimental
Note: Prior to esqueleto-3.3.4.0, the Database.Esqueleto.Experimental
module did not reexport Data.Esqueleto.
Introduction
0 declarations
This module is fully backwards-compatible extension to the esqueleto
EDSL that expands subquery functionality and enables
SQL set operations
to be written directly in Haskell. Specifically, this enables:
Subqueries in JOIN statements
UNION
UNIONALL
INTERSECT
EXCEPT
As a consequence of this, several classes of runtime errors are now
caught at compile time. This includes missing on clauses and improper
handling of Maybe values in outer joins.
This module can be used in conjunction with the main Database.Esqueleto
module, but doing so requires qualified imports to avoid ambiguous
definitions of on and from, which are defined in both modules.
Below we will give an overview of how to use this module and the
features it enables.
A New Syntax
0 declarations
This module introduces a new syntax that serves to enable the aforementioned
features. This new syntax also changes how joins written in the esqueleto
EDSL to more closely resemble the underlying SQL.
For our examples, we'll use a schema similar to the one in the Getting Started
section of Database.Esqueleto:
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persist|
Person
name String
age Int Maybe
deriving Eq Show
BlogPost
title String
authorId PersonId
deriving Eq Show
Follow
follower PersonId
followed PersonId
deriving Eq Show
|]
In this version, with each successive on clause, only the tables
we have already joined into are in scope, so we must pattern match
accordingly. In this case, in the second innerJoin, we do not use
the first Person reference, so we use _ as a placeholder to
ignore it. This prevents a possible runtime error where a table
is referenced before it appears in the sequence of JOINs.
select $ pure $ subSelectCount $
from $ \(
people
`InnerJoin` blogPosts
) -> do
on (people ^. PersonId ==. blogPosts ^. BlogPostAuthorId)
groupBy (people ^. PersonId)
having ((count $ blogPosts ^. BlogPostId) >. val 10)
pure people
select $ do
peopleWithPosts <-
from $ do
(people :& blogPosts) <-
from $ table @Person
`innerJoin` table @BlogPost
`on` (\(p :& bP) ->
p ^. PersonId ==. bP ^. BlogPostAuthorId)
groupBy (people ^. PersonId)
having ((count $ blogPosts ^. BlogPostId) >. val 10)
pure people
pure $ count (peopleWithPosts ^. PersonId)
We now have the ability to refactor this
Example 5: Sorting the results of a UNION with limits
Out of all of the posts created by a person and the people they follow,
generate a list of the first 25 posts, sorted alphabetically.
Since this module supports all set operations (see SqlSetOperation), we can use
Union to write this query.
select $ do
(authors, blogPosts) <- from $
(do
(author :& blogPost) <-
from $ table @Person
`innerJoin` table @BlogPost
`on` (\(a :& bP) ->
a ^. PersonId ==. bP ^. BlogPostAuthorId)
where_ (author ^. PersonId ==. val currentPersonId)
pure (author, blogPost)
)
`union_`
(do
(follow :& blogPost :& author) <-
from $ table @Follow
`innerJoin` table @BlogPost
`on` (\(f :& bP) ->
f ^. FollowFollowed ==. bP ^. BlogPostAuthorId)
`innerJoin` table @Person
`on` (\(_ :& bP :& a) ->
bP ^. BlogPostAuthorId ==. a ^. PersonId)
where_ (follow ^. FollowFollower ==. val currentPersonId)
pure (author, blogPost)
)
orderBy [ asc (blogPosts ^. BlogPostTitle) ]
limit 25
pure (authors, blogPosts)
Example 6: LATERAL JOIN
As of version 3.4.0.0, lateral subquery joins are supported.
select $ do
(salesPerson :& maxSaleAmount :& maxSaleCustomerName) <-
from $ table @SalesPerson
`crossJoinLateral` (\salesPerson -> do
sales <- from $ table @Sale
where_ $ sales ^. SaleSalesPersonId ==. salesPerson ^. SalesPersonId
pure $ max_ (sales ^. SaleAmount)
)
`crossJoinLateral` (\(salesPerson :& maxSaleAmount) -> do
sales <- from $ table @Sale
where_ $ sales ^. SaleSalesPersonId ==. salesPerson ^. SalesPersonId
&&. sales ^. SaleAmount ==. maxSaleAmount
pure $ sales ^. SaleCustomerName)
)
pure (salesPerson ^. SalesPersonName, maxSaleAmount, maxSaleCustomerName)
This is the equivalent to the following SQL (example taken from the
MySQL Lateral Derived Table
documentation):
SELECT
salesperson.name,
max_sale.amount,
max_sale_customer.customer_name
FROM
salesperson,
-- calculate maximum size, cache it in transient derived table max_sale
LATERAL
(SELECT MAX(amount) AS amount
FROM all_sales
WHERE all_sales.salesperson_id = salesperson.id)
AS max_sale,
LATERAL
(SELECT customer_name
FROM all_sales
WHERE all_sales.salesperson_id = salesperson.id
AND all_sales.amount =
-- the cached maximum size
max_sale.amount)
AS max_sale_customer;
Internally, this function uses the From datatype.
Unlike the old Database.Esqueleto.from, this does not
take a function as a parameter, but rather a value that
represents a JOIN tree constructed out of instances of From.
This implementation eliminates certain
types of runtime errors by preventing the construction of
invalid SQL (e.g. illegal nested-from).
Select from a subquery, often used in conjuction with joins but can be
used without any joins. Because SqlQuery has a ToFrom instance you probably
dont need to use this function directly.
select $
p <- from $
selectQuery do
p <- from $ table @Person
limit 5
orderBy [ asc p ^. PersonAge ]
...
A Lateral subquery join allows the joined query to reference entities from the
left hand side of the join. Discards rows that don't match the on clause
Join where the right side may not exist.
If the on clause fails then the right side will be NULL'ed
Because of this the right side needs to be handled as a Maybe
Used as an infix operator `leftJoin`
select $
from $ table @Person
`leftJoin` table @BlogPost
`on` (\(p :& bp) ->
just (p ^. PersonId) ==. bp ?. BlogPostAuthorId)
Lateral join where the right side may not exist.
In the case that the query returns nothing or the on clause fails the right
side of the join will be NULL'ed
Because of this the right side needs to be handled as a Maybe
Join where the left side may not exist.
If the on clause fails then the left side will be NULL'ed
Because of this the left side needs to be handled as a Maybe
Used as an infix operator `rightJoin`
select $
from $ table @Person
`rightJoin` table @BlogPost
`on` (\(p :& bp) ->
p ?. PersonId ==. bp ^. BlogPostAuthorId)
A Lateral subquery join allows the joined query to reference entities from the
left hand side of the join.
Used as an infix operator `crossJoinLateral`
See example 6
Set Operations
Data type that represents SQL set operations. This includes
UNION, UNIONALL, EXCEPT, and INTERSECT. These types form
a binary tree, with SqlQuery values on the leaves.
Each function corresponding to the aforementioned set operations
can be used as an infix in a from to help with readability
and lead to code that closely resembles the underlying SQL. For example,
select $ from $
(do
a <- from $ table A
pure $ a ^. ASomeCol
)
`union_`
(do
b <- from $ table B
pure $ b ^. BSomeCol
)
is translated into
SELECT * FROM (
(SELECT a.some_col FROM a)
UNION
(SELECT b.some_col FROM b)
)
WITH clause used to introduce a Common Table Expression (CTE).
CTEs are supported in most modern SQL engines and can be useful
in performance tuning. In Esqueleto, CTEs should be used as a
subquery memoization tactic. When writing plain SQL, CTEs
are sometimes used to organize the SQL code, in Esqueleto, this
is better achieved through function that return SqlQuery values.
select $ do
cte <- with subQuery
cteResult <- from cte
where_ $ cteResult ...
pure cteResult
WARNING: In some SQL engines using a CTE can diminish performance.
In these engines the CTE is treated as an optimization fence. You should
always verify that using a CTE will in fact improve your performance
over a regular subquery.
Notably, in PostgreSQL prior to version 12, CTEs are always fully
calculated, which can potentially significantly pessimize queries. As of
PostgreSQL 12, non-recursive and side-effect-free queries may be inlined and
optimized accordingly if not declared MATERIALIZED to get the previous
behaviour. See the PostgreSQL CTE documentation,
section Materialization, for more information. To use a MATERIALIZED query
in Esquelto, see functions withMaterialized and withRecursiveMaterialized.
WITHRECURSIVE allows one to make a recursive subquery, which can
reference itself. Like WITH, this is supported in most modern SQL engines.
Useful for hierarchical, self-referential data, like a tree of data.
select $ do
cte <- withRecursive
(do
person <- from $ table @Person
where_ $ person ^. PersonId ==. val personId
pure person
)
unionAll_
(\self -> do
(p :& f :& p2 :& pSelf) <- from self
`innerJoin` $ table @Follow
`on` (\(p :& f) ->
p ^. PersonId ==. f ^. FollowFollower)
`innerJoin` $ table @Person
`on` (\(p :& f :& p2) ->
f ^. FollowFollowed ==. p2 ^. PersonId)
`leftJoin` self
`on` (\(_ :& _ :& p2 :& pSelf) ->
just (p2 ^. PersonId) ==. pSelf ?. PersonId)
where_ $ isNothing (pSelf ?. PersonId)
groupBy (p2 ^. PersonId)
pure p2
)
from cte
Data type defining the From language. This should not
constructed directly in application code.
A From is a SqlQuery which returns a reference to the result of calling from
and a function that produces a portion of a FROM clause. This gets passed to
the FromRaw FromClause constructor directly when converting
from a From to a SqlQuery using from
GROUP BY clause. You can enclose multiple columns
in a tuple.
select $ from \(foo `InnerJoin` bar) -> do
on (foo ^. FooBarId ==. bar ^. BarId)
groupBy (bar ^. BarId, bar ^. BarName)
return (bar ^. BarId, bar ^. BarName, countRows)
With groupBy you can sort by aggregate functions, like so
(we used let to restrict the more general countRows to
SqlSqlExpr (Value Int) to avoid ambiguity---the second use of
countRows has its type restricted by the :: Int below):
r <- select $ from \(foo `InnerJoin` bar) -> do
on (foo ^. FooBarId ==. bar ^. BarId)
groupBy $ bar ^. BarName
let countRows' = countRowsorderBy [asc countRows']
return (bar ^. BarName, countRows')
forM_ r $ \(Value name, Value count) -> do
print name
print (count :: Int)
Need more columns?
The ToSomeValues class is defined for SqlExpr and tuples of SqlExprs.
We only have definitions for up to 8 elements in a tuple right now, so it's
possible that you may need to have more than 8 elements.
For example, consider a query with a groupBy call like this:
groupBy (e0, e1, e2, e3, e4, e5, e6, e7)
This is the biggest you can get with a single tuple. However, you can easily
nest the tuples to add more:
Note that PostgreSQL requires the SqlExpressions on DISTINCT
ON to be the first ones to appear on a ORDER BY. This is
not managed automatically by esqueleto, keeping its spirit
of trying to be close to raw SQL.
Deprecated. This function is deprecated, as it is only supported in Postgresql. Please use the function defined in Database.Esqueleto.PostgreSQL instead.
A convenience function that calls both distinctOn and
orderBy. In other words,
Add a locking clause to the query. Please read
LockingKind documentation and your RDBMS manual.
Unsafe since not all locking clauses are implemented for every RDBMS
If multiple calls to locking are made on the same query,
the last one is used.
In SQL, = NULL and != NULL return NULL instead of true or false. For this reason, you very likely do not want to use !=. Nothing in Esqueleto.
You may find these hlint rules helpful to enforce this:
- error: {lhs: v Database.Esqueleto.==. Database.Esqueleto.nothing, rhs: Database.Esqueleto.isNothing v, name: Use Esqueleto's isNothing}
- error: {lhs: v Database.Esqueleto.==. Database.Esqueleto.val Nothing, rhs: Database.Esqueleto.isNothing v, name: Use Esqueleto's isNothing}
- error: {lhs: v Database.Esqueleto.!=. Database.Esqueleto.nothing, rhs: not_ (Database.Esqueleto.isNothing v), name: Use Esqueleto's not isNothing}
- error: {lhs: v Database.Esqueleto.!=. Database.Esqueleto.val Nothing, rhs: not_ (Database.Esqueleto.isNothing v), name: Use Esqueleto's not isNothing}
Analogous to Just, promotes a value of type typ into
one of type Maybe typ. It should hold that val . Just
=== just . val.
This function will try not to produce a nested Maybe. This is in accord
with how SQL represents NULL. That means that just . just = just.
This behavior was changed in v3.6.0.0. If you want to produce nested Maybe,
see just'.
Join nested Maybes in a Value into one. This is useful when
calling aggregate functions on nullable fields.
As of v3.6.0.0, this function will attempt to work on both SqlExpr
(Value (Maybe a)) as well as SqlExpr (Value (Maybe (Maybe a)))
inputs to make transitioning to NullableFieldProjection easier. This may
make type inference worse in some cases. If you want the monomorphic variant,
see joinV'
Allow a number of one type to be used as one of another
type via an implicit cast. An explicit cast is not made,
this function changes only the types on the Haskell side.
Caveat: Trying to use castNum from Double to Int
will not result in an integer, the original fractional
number will still be used! Use round_, ceiling_ or
floor_ instead.
Safety: This operation is mostly safe due to the Num
constraint between the types and the fact that RDBMSs
usually allow numbers of different types to be used
interchangeably. However, there may still be issues with
the query not being accepted by the RDBMS or persistent
not being able to parse it.
COALESCE function. Evaluates the arguments in order and
returns the value of the first non-NULL SqlExpression, or NULL
(Nothing) otherwise. Some RDBMSs (such as SQLite) require
at least two arguments; please refer to the appropriate
documentation.
The string %. May be useful while using like and
concatenation (concat_ or ++., depending on your
database). Note that you always have to type the parenthesis,
for example:
The CONCAT function with a variable number of
parameters. Supported by MySQL and PostgreSQL. SQLite supports this in
versions after 3.44.0, and persistent-sqlite supports this in versions
2.13.3.0 and after.
Cast a string type into Text. This function
is very useful if you want to use newtypes, or if you want
to apply functions such as like to strings of different
types.
Safety: This is a slightly unsafe function, especially if
you have defined your own instances of SqlString. Also,
since Maybe is an instance of SqlString, it's possible
to turn a nullable value into a non-nullable one. Avoid
using this function if possible.
This query is a bit complicated, but basically it checks if a person
named "Mike" exists, and if that person does, run the subquery to find
out how many people have a ranking (by Fav Num) higher than "Mike".
NOTE: There are a few things to be aware about this statement.
This only implements the full CASE statement, it does not
implement the "simple" CASE statement.
At least one when_ and then_ is mandatory otherwise it will
emit an error.
The else_ is also mandatory, unlike the SQL statement in which
if the ELSE is omitted it will return a NULL. You can
reproduce this via nothing.
The inverse of toBaseId. Note that this is somewhat less "safe" than
toBaseId. Calling toBaseId will usually mean that a foreign key
constraint is present that guarantees the presence of the base ID.
fromBaseId has no such guarantee. Consider the code example given in
toBaseId:
Bar
barNum Int
Foo
bar BarId
fooNum Int
Primary bar
instance ToBaseId Foo where
type BaseEnt Foo = Bar
toBaseIdWitness barId = FooKey barId
Execute a subquery SELECT in a SqlExpr. The query passed to this
function will only return a single result - it has a LIMIT 1 passed in to
the query to make it safe, and the return type is Maybe to indicate that
the subquery might result in 0 rows.
If you know that the subquery will always return exactly one row (eg
a foreign key constraint guarantees that you'll get exactly one row), then
consider subSelectUnsafe, along with a comment explaining why it is safe.
Execute a subquery SELECT in a SqlExpr. This function is a shorthand
for the common joinV . subSelect idiom, where you are calling
subSelect on an expression that would be Maybe already.
As an example, you would use this function when calling sum_ or max_,
which have Maybe in the result type (for a 0 row query).
Execute a subquery SELECT in a SqlExpr that returns a list. This is an
alias for subList_select and is provided for symmetry with the other safe
subselect functions.
Execute a subquery SELECT in a SqlExpr. This function is unsafe,
because it can throw runtime exceptions in two cases:
If the query passed has 0 result rows, then it will return a NULL value.
The persistent parsing operations will fail on an unexpected NULL.
If the query passed returns more than one row, then the SQL engine will
fail with an error like "More than one row returned by a subquery used as
an expression".
This function is safe if you guarantee that exactly one row will be returned,
or if the result already has a Maybe type for some reason.
Convert from the key of the BaseEnt(ity) to the key of the child entity.
This function is not actually called, but that it typechecks proves this operation is safe.
This instance allows you to use record.field notation with GC 9.2's
OverloadedRecordDot extension.
Example:
-- persistent model:
Person
name Text
BlogPost
title Text
authorId PersonId
-- query:
select $ do
(p :& bp) <- from $
tablePerson
leftJoin table BlogPost
on do
\(p :& bp) ->
just p.id ==. bp.authorId
pure (p.name, bp.title)
Different kinds of locking clauses supported by locking.
Note that each RDBMS has different locking support. The
constructors of this datatype specify only the syntax of the
locking mechanism, not its semantics. For example, even
though both MySQL and PostgreSQL support ForUpdate, there
are no guarantees that they will behave the same.
select $ do
(p :& bp) <- from $
table Person
innerJoin table BlogPost
on do
(p :& bp) -> p ^. PersonId ==. b ^. BlogPostAuthorId
forUpdateOf (p :& b) skipLocked
return p
Phantom class of data types that are treated as strings by the
RDBMS. It has no methods because it's only used to avoid type
errors such as trying to concatenate integers.
If you have a custom data type or newtype, feel free to make
it an instance of this class.
Instances6SqlString
SqlStringHtmlDefined in esqueleto-3.6.0.3 · Database.Esqueleto.Internal.Internal
SqlStringByteStringDefined in esqueleto-3.6.0.3 · Database.Esqueleto.Internal.Internal
SqlStringTextDefined in esqueleto-3.6.0.3 · Database.Esqueleto.Internal.Internal
SqlStringTextDefined in esqueleto-3.6.0.3 · Database.Esqueleto.Internal.Internal
SqlStringa => SqlString (Maybea)Defined in esqueleto-3.6.0.3 · Database.Esqueleto.Internal.Internal
a~Char => SqlString [a]Defined in esqueleto-3.6.0.3 · Database.Esqueleto.Internal.Internal
This example is a bit trivial, but once you've joined five or six tables it
becomes enormously helpful. The above example uses a ViewPattern to call
the function and assign the variable directly, but you can also imagine it
being written like this:
`on` (\(prev :& blogPost) ->
let
follow = getTable @Follow prev
in
blogPost ^. BlogPostAuthorId ==. follow ^. FollowFollower)
This function will pluck out the first table that matches the applied type,
so if you join on the same table multiple times, it will always select the
first one provided.
The (:&) operator associates so that the left hand side can be a wildcard
for an arbitrary amount of nesting, and the "most recent" or "newest" table
in a join sequence is always available on the rightmost - so (prev :& bar)
is a pattern that matches bar table (the most recent table added) and
prev tables (all prior tables in the join match).
By calling getTable on the prev, you can select exactly the table you
want, allowing you to omit a large number of spurious pattern matches.
Consider a query that does several LEFT JOIN on a first table:
SELECT *
FROM person
LEFT JOIN car
ON person.id = car.person_id
LEFT JOIN bike
ON person.id = bike.person_id
LEFT JOIN food
ON person.id = food.person_id
LEFT JOIN address
ON person.id = address.person_id
The final on clause in esqueleto would look like this:
First, we can change it to a prev :& newest match. We can do this because
of the operator associativity. This is kind of like how a list : operator
associates, but in the other direction: a : (b : c) = a : b : c.
`on` do
\(prev :& address) ->
let (person :& _car :& _bike :& _food) = prev
in person.id ==. address.personId
Then, we can use getTable to select the Person table directly, instead of
pattern matching manually.
`on` do
\(prev :& address) ->
let person = getTable @Person prev
in person.id ==. address.personId
Finally, we can use a ViewPattern language extension to "inline" the
access.
Raw expression: Contains a SqlExprMeta and a function for
building the expr. It recieves a parameter telling it whether
it is in a parenthesized context, and takes information about the SQL
connection (mainly for escaping names) and returns both an
string (Builder) and a list of values to be
interpolated by the SQL backend.
Folks often want the ability to promote a Haskell function into the
SqlExpr expression language - and naturally reach for fmap.
Unfortunately, this is impossible. We cannot send *functions* to the
database, which is what we would need to do in order for this to make sense.
Let's consider the type of fmap for SqlExpr:
This type signature is making a pretty strong claim: "Give me a Haskell
function from a -> b. I will then transform a SQL expression representing
a Haskell value of type a and turn it into a SQL expression representing
a Haskell value of type b."
Let's suppose we *could* do this - fmap (+1) would have to somehow inspect
the function expression means "add one", and then translate that to the
appropriate SQL.
This is why esqueleto defines a bunch of operators: x +. (val 1) can
be used instead of fmap (+1) x.
This instance allows you to use record.field notation with GC 9.2's
OverloadedRecordDot extension.
Example:
-- persistent model:
Person
name Text
BlogPost
title Text
authorId PersonId
-- query:
select $ do
(p :& bp) <- from $
tablePerson
leftJoin table BlogPost
on do
\(p :& bp) ->
just p.id ==. bp.authorId
pure (p.name, bp.title)
Execute an esqueletoSELECT query inside persistent's
SqlPersistT monad and return a list of rows.
We've seen that from has some magic about which kinds of
things you may bring into scope. This select function also
has some magic for which kinds of things you may bring back to
Haskell-land by using SqlQuery's return:
You may return a SqlExpr (Entity v) for an entity v
(i.e., like the * in SQL), which is then returned to
Haskell-land as just Entity v.
You may return a SqlExpr (Maybe (Entity v)) for an entity
v that may be NULL, which is then returned to
Haskell-land as Maybe (Entity v). Used for OUTER JOINs.
You may return a SqlExpr (Value t) for a value t
(i.e., a single column), where t is any instance of
PersistField, which is then returned to Haskell-land as
Value t. You may use Value to return projections of an
Entity (see (^.) and (?.)) or to return any other
value calculated on the query (e.g., countRows or
subSelect).
The SqlSelect a r class has functional dependencies that
allow type information to flow both from a to r and
vice-versa. This means that you'll almost never have to give
any type signatures for esqueleto queries. For example, the
query select $ from $ \p -> return p alone is ambiguous, but
in the context of
do ps <- select $
from $ \p ->
return p
liftIO $ mapM_ (putStrLn . personName . entityVal) ps
we are able to infer from that single personName . entityVal
function composition that the p inside the query is of type
SqlExpr (Entity Person).
Execute an esqueletoDELETE query inside persistent's
SqlPersistT monad. Note that currently there are no type
checks for statements that should not appear on a DELETE
query.
Unlike select, there is a useful way of using delete that
will lead to type ambiguities. If you want to delete all rows
(i.e., no where_ clause), you'll have to use a type signature:
Execute an esqueletoUPDATE query inside persistent's
SqlPersistT monad. Note that currently there are no type
checks for statements that should not appear on a UPDATE
query.
Example of usage:
update $ \p -> do
set p [ PersonAge =.just (val thisYear) -. p ^. PersonBorn ]
where_ $ isNothing (p ^. PersonAge)
Renders a SqlQuery into a Text value along with the list of
PersistValues that would be supplied to the database for ? placeholders.
You must ensure that the Mode you pass to this function corresponds with
the actual SqlQuery. If you pass a query that uses incompatible features
(like an INSERT statement with a SELECT mode) then you'll get a weird
result.
valJ is like val but for something that is already a Value. The use
case it was written for was, given a Value lift the Key for that Value
into the query expression in a type safe way. However, the implementation is
more generic than that so we call it valJ.
Avoid N+1 queries and join entities into a map structure.
This function is useful to call on the result of a single JOIN. For
example, suppose you have this query:
getFoosAndNestedBarsFromParent
:: ParentId
-> SqlPersistT IO [(Entity Foo, Maybe (Entity Bar))]
getFoosAndNestedBarsFromParent parentId =
select $ do
(foo :& bar) <- from $
table Foo
`LeftOuterJoin`
table Bar
`on` do
\(foo :& bar) ->
foo ^. FooId ==. bar ?. BarFooId
where_ $
foo ^. FooParentId ==. val parentId
pure (foo, bar)
This is a natural result type for SQL - a list of tuples. However, it's not
what we usually want in Haskell - each Foo in the list will be represented
multiple times, once for each Bar.
We can write fmapassociateJoin and it will translate it into a Map
that is keyed on the Key of the left Entity, and the value is a tuple of
the entity's value as well as the list of each coresponding entity.
userPostComments
:: SqlQuery (SqlExpr (Entity User, Entity Post, Entity Comment))
userPostsComment = do
(u :& p :& c) <- from $
table User
`InnerJoin`
table Post
on do
\(u :& p) ->
u ^. UserId ==. p ^. PostUserId
`InnerJoin`
table @Comment
`on` do
\(_ :& p :& c) ->
p ^. PostId ==. c ^. CommentPostId
pure (u, p, c)
This query returns a User, with all of the users Posts, and then all of the
Comments on that post.
First, we *nest* the tuple.
nest :: (a, b, c) -> (a, (b, c))
nest (a, b, c) = (a, (b, c))
This makes the return of the query conform to the input expected from
associateJoin.
Convenience function for getting a free PersistField instance
from a type with JSON instances. The JSON parser used will accept JSON
values other that object and arrays. So, if your instance serializes the
data to a JSON string, this will still work.
This is a specialization of tabulateEntityA, which allows you to
construct an Entity by providing an Applicative action for each
field instead of a regular function.
Check whether there are any conflicts for unique keys with this entity and
existing entities in the database.
Returns Nothing if the entity would stay unique, and could thus safely be updated.
on a conflict returns the conflicting key
This is similar to checkUnique, except it's useful for updating - when the
particular entity already exists, it would normally conflict with itself.
This variant ignores those conflicts
A modification of getBy, which takes the PersistEntity itself instead
of a Unique record. Returns a record matching one of the unique keys. This
function makes the most sense on entities with a single Unique
constructor.
Insert a value, checking for conflicts with any unique constraints. If a
duplicate exists in the database, it is returned as Left. Otherwise, the
new 'Key is returned as Right.
onlySimonConst :: MonadIO m => ReaderT SqlBackend m (Unique User)
onlySimonConst = onlyUnique $ User "Simon" 999
mSimonConst <- onlySimonConst
mSimonConst would be Simon's uniqueness constraint. Note that
onlyUnique doesn't work if there're more than two constraints. It will
fail with a type error instead.
Attempt to replace the record of the given key with the given new record.
First query the unique fields to make sure the replacement maintains
uniqueness constraints.
Return Nothing if the replacement was made.
If uniqueness is violated, return a Just with the Unique violation
Retrieve the list of FieldDef that makes up the fields of the entity.
This does not return the fields for an Id column or an implicit id. It
will return the key columns if you used the Primary syntax for defining the
primary key.
This does not return fields that are marked SafeToRemove or MigrationOnly
- so it only returns fields that are represented in the Haskell type. If you
need those fields, use getEntityFieldsDatabase.
This returns all of the FieldDef defined for the EntityDef, including
those fields that are marked as MigrationOnly (and therefore only present
in the database) or SafeToRemove (and a migration will drop the column if
it exists in the database).
For all the fields that are present on the Haskell-type, see
getEntityFields.
Note that as of this writing the span covers the entire file or quasiquote
where the item is defined due to parsing limitations. This may be changed in
a future release to be more accurate.
Retrieve the list of UniqueDef from an EntityDef. As of version 2.14,
this will also include the primary key on the entity, if one is defined. If
you do not want the primary key, see getEntityUniquesNoPrimaryKey.
Retrieve the list of UniqueDef from an EntityDef. This does not include
a Primary key, if one is defined. A future version of persistent will
include a Primary key among the Unique constructors for the Entity.
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.
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"
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.
Is the migration unsafe to run? (eg a destructive or non-idempotent
update on the schema). If True, the migration is *unsafe*, and will
need to be run manually later. If False, the migration is *safe*, and
can be run any number of times.
Same as runMigration, but does not report the individual migrations on
stderr. Instead it returns a list of the executed SQL commands.
This is a safer/more robust alternative to runMigrationSilent, but may be
less silent for some persistent implementations, most notably
persistent-postgresql
Same as runMigration, but returns a list of the SQL commands executed
instead of printing them to stderr.
This function silences the migration by remapping stderr. As a result, it
is not thread-safe and can clobber output from other parts of the program.
This implementation method was chosen to also silence postgresql migration
output on stderr, but is not recommended!
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.
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)
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.
Returns a NonEmpty list of FieldDef that correspond with the key
columns for an EntityDef including those fields that are marked as
MigrationOnly (and therefore only present in the database) or
SafeToRemove (and a migration will drop the column if it exists in the
database).
Parse raw field attributes into structured form. Any unrecognized
attributes will be preserved, identically as they are encountered,
as FieldAttrOther values.
A backwards-compatible alias for those that don't care about distinguishing between read and write queries.
It signifies the assumption that, by default, a backend can write as well as read.
A backwards-compatible alias for those that don't care about distinguishing between read and write queries.
It signifies the assumption that, by default, a backend can write as well as read.
Represents a value containing all the configuration options for a specific
backend. This abstraction makes it easier to write code that can easily swap
backends.
Datatype that represents an entity, with both its Key and
its Haskell record representation.
When using a SQL-based backend (such as SQLite or
PostgreSQL), an Entity may take any number of columns
depending on how many fields it has. In order to reconstruct
your entity on the Haskell side, persistent needs all of
your entity columns and in the right order. Note that you
don't need to worry about this when using persistent's API
since everything is handled correctly behind the scenes.
However, if you want to issue a raw SQL command that returns
an Entity, then you have to be careful with the column
order. While you could use SELECT Entity.* WHERE ... and
that would work most of the time, there are times when the
order of the columns on your database is different from the
order that persistent expects (for example, if you add a new
field in the middle of you entity definition and then use the
migration code -- persistent will expect the column to be in
the middle, but your DBMS will put it as the last column).
So, instead of using a query like the one above, you may use
rawSql (from the
Database.Persist.Sql module) with its /entity
selection placeholder/ (a double question mark ??). Using
rawSql the query above must be written as SELECT ?? WHERE
... Then rawSql will replace ?? with the list of all
columns that we need from your entity in the right order. If
your query returns two entities (i.e. (Entity backend a,
Entity backend b)), then you must you use SELECT ??, ??
WHERE ..., and so on.
This instance allows you to use record.field notation with GC 9.2's
OverloadedRecordDot extension.
Example:
-- persistent model:
Person
name Text
BlogPost
title Text
authorId PersonId
-- query:
select $ do
(p :& bp) <- from $
tablePerson
leftJoin table BlogPost
on do
\(p :& bp) ->
just p.id ==. bp.authorId
pure (p.name, bp.title)
An EntityField is parameterised by the Haskell record it belongs to
and the additional type of that field.
As of persistent-2.11.0.0, it's possible to use the OverloadedLabels
language extension to refer to EntityField values polymorphically. See
the documentation on SymbolToField for more information.
Instances1IsLabel
SymbolToFieldsymrectyp => IsLabelsym (EntityFieldrectyp)Defined in persistent-2.18.1.0 · Database.Persist.Class.PersistEntity
This instance delegates to SymbolToField to provide
OverloadedLabels support to the EntityField type.
Persistent serialized Haskell records to the database.
A Database Entity (A row in SQL, a document in MongoDB, etc)
corresponds to a Key plus a Haskell record.
For every Haskell record type stored in the database there is a
corresponding PersistEntity instance. An instance of PersistEntity
contains meta-data for the record. PersistEntity also helps abstract
over different record types. That way the same query interface can return
a PersistEntity, with each query returning different types of Haskell
records.
Some advanced type system capabilities are used to make this process
type-safe. Persistent users usually don't need to understand the class
associated data and functions.
An EntityField is parameterised by the Haskell record it belongs to
and the additional type of that field.
As of persistent-2.11.0.0, it's possible to use the OverloadedLabels
language extension to refer to EntityField values polymorphically. See
the documentation on SymbolToField for more information.
This function allows you to build an Entity a by specifying an
action that returns a value for the field in the callback function.
Let's look at an example.
parseFromEnvironmentVariables :: IO (Entity User)
parseFromEnvironmentVariables =
tabulateEntityA $ \userField ->
case userField of
UserName ->
getEnv USER_NAME
UserAge -> do
ageVar <- getEnv USER_AGE
case readMaybe ageVar of
Just age ->
pure age
Nothing ->
error $ "Failed to parse Age from: " <> ageVar
UserAddressId -> do
addressVar <- getEnv USER_ADDRESS_ID
pure $ AddressKey addressVar
Like tabulateEntityA, but works with any Apply f. This works
because all entities have at least one field, and so we can tabulate
things into semigroup-like shapes instead.
Extract a Key record from a record value. Currently, this is
only defined for entities using the Primary syntax for
natural/composite keys. In a future version of persistent which
incorporates the ID directly into the entity, this will always be Just.
A type class which is used to witness that a type is safe to insert into
the database without providing a primary key.
The TemplateHaskell function mkPersist will generate instances of this
class for any entity that it works on. If the entity has a default primary
key, then it provides a regular instance. If the entity has a Primary
natural key, then this works fine. But if the entity has an Id column with
no default=, then this does a TypeError and forces the user to use
insertKey.
Instances2SafeToInsert
TypeError (EntityErrorMessagea) => SafeToInsert (Entitya)Defined in persistent-2.18.1.0 · Database.Persist.Class.PersistEntity
TypeError (FunctionErrorMessageab) => SafeToInsert (a -> b)Defined in persistent-2.18.1.0 · Database.Persist.Class.PersistEntity
This type class is used with the OverloadedLabels extension to
provide a more convenient means of using the EntityField type.
EntityField definitions are prefixed with the type name to avoid
ambiguity, but this ambiguity can result in verbose code.
If you have a table User with a name Text field, then the
corresponding EntityField is UserName. With this, we can write
#name :: EntityField User Text.
What's more fun is that the type is more general: it's actually
#name
:: (SymbolToField "name" rec typ)
=> EntityField rec typ
Which means it is *polymorphic* over the actual record. This allows you
to write code that can be generic over the tables, provided they have
the right fields.
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.
In this example, we create a PersistField instance for a newtype following the "Smart Constructor" pattern.
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import qualified Data.Text as T
import qualified Data.Char as C
-- | An American Social Security Number
newtype SSN = SSN Text
deriving (Eq, Show, PersistFieldSql)
mkSSN :: Text -> EitherText SSN
mkSSN t = if (T.length t == 9) && (T.all C.isDigit t)
then Right $ SSN t
else Left $ "Invalid SSN: " <> t
instance PersistField SSN where
toPersistValue (SSN t) = PersistText t
fromPersistValue (PersistText t) = mkSSN t
-- Handle cases where the database does not give us PersistText
fromPersistValue x = Left $ "File.hs: When trying to deserialize an SSN: expected PersistText, received: " <> T.pack (show x)
Tips:
This file contain dozens of PersistField instances you can look at for examples.
TypeError ((((('Text"The instance of PersistField for the Natural type was removed." ':$$: 'Text"Please see the documentation for OverflowNatural if you want to ") ':$$: 'Text"continue using the old behavior or want to see documentation on ") ':$$: 'Text"why the instance was removed.") ':$$: 'Text"") ':$$: 'Text"This error instance will be removed in a future release.") => PersistFieldNaturalDefined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldHtmlDefined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldByteStringDefined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldInt16Defined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldInt32Defined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldInt64Defined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldInt8Defined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldRationalDefined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldBoolDefined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldDoubleDefined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldIntDefined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldWordDefined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldWord16Defined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldWord32Defined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldWord64Defined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
PersistFieldWord8Defined in persistent-2.18.1.0 · Database.Persist.Class.PersistField
Get all records matching the given criterion in the specified order.
Returns also the identifiers.
NOTE: This function returns an Acquire and a ConduitM, which implies
that it streams from the database. It does not. Please use selectList
to simplify the code. If you want streaming behavior, consider
persistent-pagination which efficiently chunks a query into ranges, or
investigate a backend-specific streaming solution.
This class witnesses that two backend are compatible, and that you can
convert from the sub backend into the sup backend. This is similar
to the HasPersistBackend and IsPersistBackend classes, but where you
don't want to fix the type associated with the PersistEntityBackend of
a record.
This works for SqlReadBackend because of the instance BackendCompatibleSqlBackendSqlReadBackend, without needing to go through the BaseBackend type family.
Likewise, functions that are currently hardcoded to use SqlBackend can be generalized:
Class which allows the plucking of a BaseBackend backend from some larger type.
For example,
instance HasPersistBackend (SqlReadBackend, Int) where
type BaseBackend (SqlReadBackend, Int) = SqlBackend
persistBackend = unSqlReadBackend . fst
Class which witnesses that backend is essentially the same as BaseBackend backend.
That is, they're isomorphic and backend is just some wrapper over BaseBackend backend.
Replace the record in the database with the given
key. Note that the result is undefined if such record does
not exist, so you must use insertKey or repsert in
these cases.
ToBackendKey converts a PersistEntityKey into a BackendKey
This can be used by each backend to convert between a Key and a plain
Haskell type. For Sql, that is done with toSqlKey and fromSqlKey.
By default, a PersistEntity uses the default BackendKey for its Key
and is an instance of ToBackendKey
A Key that instead uses a custom type will not be an instance of
ToBackendKey.
This class is used to ensure that functions requring at least one
unique key are not called with records that have 0 unique keys. The
quasiquoter automatically writes working instances for appropriate
entities, and generates TypeError instances for records that have
0 unique keys.
StypetypeMultipleUniqueKeysErrorty = ((('Text"The entity " ':<>: 'ShowTypety) ':<>: 'Text" has multiple unique keys.") ':$$: ('Text"The function you are trying to call requires only a single " ':<>: 'Text"unique key.")) ':$$: (('Text"There is probably a variant of the function with 'By' " ':<>: 'Text"appended that will allow you to select a unique key ") ':<>: 'Text"for the operation.")
This is an error message. It is used when an entity has multiple
unique keys, and the function expects a single unique key.
StypetypeNoUniqueKeysErrorty = (('Text"The entity " ':<>: 'ShowTypety) ':<>: 'Text" does not have any unique keys.") ':$$: ('Text"The function you are trying to call requires a unique key " ':<>: 'Text"to be defined on the entity.")
This class is used to ensure that upsert is only called on records
that have a single Unique key. The quasiquoter automatically generates
working instances for appropriate records, and generates TypeError
instances for records that have 0 or multiple unique keys.
Queries against Unique keys (other than the id Key).
Please read the general Persistent documentation to learn how to create
Unique keys.
Using this with an Entity without a Unique key leads to undefined
behavior. A few of these functions require a singleUnique, so using
an Entity with multiple Uniques is also undefined. In these cases
persistent's goal is to throw an exception as soon as possible, but
persistent is still transitioning to that.
SQL backends automatically create uniqueness constraints, but for MongoDB
you must manually place a unique index on a field to have a uniqueness
constraint.
Some functions in this module (insertUnique, insertBy, and
replaceUnique) first query the unique indexes to check for
conflicts. You could instead optimistically attempt to perform the
operation (e.g. replace instead of replaceUnique). However,
there is some fragility to trying to catch the correct exception and
determing the column of failure;
an exception will automatically abort the current SQL transaction.
Update based on a uniqueness constraint or insert:
insert the new record if it does not exist;
If the record exists (matched via it's uniqueness constraint), then update the existing record with the parameters which is passed on as list to the function.
Next, what if the schema has two uniqueness constraints?
Let's check it out using schema-2:
mSpjEnt <- upsertSpj [UserAge +=. 15]
This fails with a compile-time type error alerting us to the fact
that this record has multiple unique keys, and suggests that we look for
upsertBy to select the unique key we want.
upsertBySpjName :: MonadIO m => User -> [Update User] -> ReaderT SqlBackend m (Entity User)
upsertBySpjName record updates = upsertBy (UniqueUserName "SPJ") record updates
mSpjEnt <- upsertBySpjName (Person "X" 999) [PersonAge += .15]
upsertBySimonAge :: MonadIO m => User -> [Update User] -> ReaderT SqlBackend m (Entity User)
upsertBySimonAge record updates = upsertBy (UniqueUserName "SPJ") record updates
mPhilipEnt <- upsertBySimonAge (User "X" 999) [UserName =. "Philip"]
+----+-----------------+-----+
| id | name | age |
+----+-----------------+-----+
| 1 | SPJ | 40 |
+----+-----------------+-----+
| 2 | Simon -> Philip | 41 |
+----+-----------------+-----+
upsertByUnknownName :: MonadIO m => User -> [Update User] -> ReaderT SqlBackend m (Entity User)
upsertByUnknownName record updates = upsertBy (UniqueUserName "Unknown") record updates
mXEnt <- upsertByUnknownName (User "X" 999) [UserAge +=. 15]
The DbSpecific constructor corresponds to the legacy
PersistDbSpecific constructor. We need to keep this around because
old databases may have serialized JSON representations that
reference this. We don't want to break the ability of a database to
load rows.
Instances4Eq, Ord, Read, Show
EqLiteralTypeDefined in persistent-2.18.1.0 · Database.Persist.PersistValue
OrdLiteralTypeDefined in persistent-2.18.1.0 · Database.Persist.PersistValue
ReadLiteralTypeDefined in persistent-2.18.1.0 · Database.Persist.PersistValue
ShowLiteralTypeDefined in persistent-2.18.1.0 · Database.Persist.PersistValue
This constructor is used to specify some raw literal value for the
backend. The LiteralType value specifies how the value should be
escaped. This can be used to make special, custom types avaialable
in the back end.
Deprecated. Deprecated since 2.11 because of inconsistent escaping behavior across backends. The Postgres backend escapes these values, while the MySQL backend does not. If you are using this, please switch to PersistLiteral_ and provide a relevant LiteralType for your conversion.
This pattern synonym used to be a data constructor for the
PersistValue type. It was changed to be a pattern so that JSON-encoded
database values could be parsed into their corresponding values. You
should not use this, and instead prefer to pattern match on
PersistLiteral_ directly.
If you use this, it will overlap a patern match on the 'PersistLiteral_,
PersistLiteral, and PersistLiteralEscaped patterns. If you need to
disambiguate between these constructors, pattern match on
PersistLiteral_ directly.
This pattern synonym used to be a data constructor on PersistValue,
but was changed into a catch-all pattern synonym to allow backwards
compatiblity with database types. See the documentation on
PersistDbSpecific for more details.
This pattern synonym used to be a data constructor on PersistValue,
but was changed into a catch-all pattern synonym to allow backwards
compatiblity with database types. See the documentation on
PersistDbSpecific for more details.
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.
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.
A list of SQL operations, marked with a safety flag. If the Bool is
True, then the operation is *unsafe* - it might be destructive, or
otherwise not idempotent. If the Bool is False, then the operation
is *safe*, and can be run repeatedly without issues.
An exception indicating that Persistent refused to run some unsafe
migrations. Contains a list of pairs where the Bool tracks whether the
migration was unsafe (True means unsafe), and the Sql is the sql statement
for the migration.
This Show instance renders an error message suitable for printing to the
console. This is a little dodgy, but since GHC uses Show instances when
displaying uncaught exceptions, we have little choice.
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.
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.
A Checkmark should be used as a field type whenever a
uniqueness constraint should guarantee that a certain kind of
record may appear at most once, but other kinds of records may
appear any number of times.
NOTE: You need to mark any Checkmark fields as nullable
(see the following example).
For example, suppose there's a Location entity that
represents where a user has lived:
Location
user UserId
name Text
current Checkmark nullable
UniqueLocation user current
The UniqueLocation constraint allows any number of
InactiveLocations to be current. However, there may be
at most one currentLocation per user (i.e., either zero
or one per user).
This data type works because of the way that SQL treats
NULLable fields within uniqueness constraints. The SQL
standard says that NULL values should be considered
different, so we represent Inactive as SQL NULL, thus
allowing any number of Inactive records. On the other hand,
we represent Active as TRUE, so the uniqueness constraint
will disallow more than one Active record.
Note: There may be DBMSs that do not respect the SQL
standard's treatment of NULL values on uniqueness
constraints, please check if this data type works before
relying on it.
The SQL BOOLEAN type is used because it's the smallest data
type available. Note that we never use FALSE, just TRUE
and NULL. Provides the same behavior Maybe () would if
() was a valid PersistField.
An EntityDef represents the information that persistent knows
about an Entity. It uses this information to generate the Haskell
datatype, the SQL migrations, and other relevant conversions.
Instances5Eq, Ord, Read, Show, Lift
EqEntityDefDefined in persistent-2.18.1.0 · Database.Persist.Types.Base
OrdEntityDefDefined in persistent-2.18.1.0 · Database.Persist.Types.Base
ReadEntityDefDefined in persistent-2.18.1.0 · Database.Persist.Types.Base
ShowEntityDefDefined in persistent-2.18.1.0 · Database.Persist.Types.Base
LiftEntityDefDefined in persistent-2.18.1.0 · Database.Persist.Types.Base
This indicates that the column is nullable, but should not have
a Maybe type. For this to work out, you need to ensure that the
PersistField instance for the type in question can support
a PersistNull value.
data What = NoWhat | Hello Text
instance PersistField What where
fromPersistValue PersistNull =
pure NoWhat
fromPersistValue pv =
Hello $ fromPersistValue pv
instance PersistFieldSql What where
sqlType _ = SqlString
User
what What nullable
This tag means that the column will not be present on the Haskell code,
but will not be removed from the database. Useful to deprecate fields in
phases.
You should set the column to be nullable in the database. Otherwise,
inserts won't have values.
A SafeToRemove attribute is not present on the Haskell datatype, and
the backend migrations should attempt to drop the column without
triggering any unsafe migration warnings.
Useful after you've used MigrationOnly to remove a column from the
database in phases.
This attribute indicates that we should not create a foreign key
reference from a column. By default, persistent will try and create a
foreign key reference for a column if it can determine that the type of
the column is a Key entity or an EntityId and the Entity's name
was present in mkPersist.
This is useful if you want to use the explicit foreign key syntax.
Post
title Text
Comment
postId PostId noreference
Foreign Post fk_comment_post postId
This is set to specify precisely the database table the column refers
to.
Post
title Text
Comment
postId PostId references="post"
You should not need this - persistent should be capable of correctly
determining the target table's name. If you do need this, please file an
issue describing why.
This datatype describes how a foreign reference field cascades deletes
or updates.
This type is used in both parsing the model definitions and performing
migrations. A Nothing in either of the field values means that the
user has not specified a CascadeAction. An unspecified CascadeAction
is defaulted to Restrict when doing migrations.
A FieldDef represents the inormation that persistent knows about
a field of a datatype. This includes information used to parse the field
out of the database and what the field corresponds to.
The name of the field. Note that this does not corresponds to the
record labels generated for the particular entity - record labels
are generated with the type name prefixed to the field, so
a FieldDef that contains a FieldNameHS "name" for a type
User will have a record field userName.
Defines how operations on the field cascade on to the referenced
tables. This doesn't have any meaning if the fieldReference is set
to NoReference or SelfReference. The cascade option here should
be the same as the one obtained in the fieldReference.
A SQL data type. Naming attempts to reflect the underlying Haskell
datatypes, eg SqlString instead of SqlVarchar. Different SQL databases may
have different translations for these types.
The reason why a field is nullable is very important. A
field that is nullable because of a Maybe tag will have its
type changed from A to Maybe A. OTOH, a field that is
nullable because of a nullable tag will remain with the same
type.