A lot more docs.
This commit is contained in:
parent
df63cd864b
commit
bc21bac9ca
@ -38,6 +38,9 @@ description:
|
|||||||
functions), so please open an issue or send a pull request if
|
functions), so please open an issue or send a pull request if
|
||||||
you need anything that is not covered by @esqueleto@ on
|
you need anything that is not covered by @esqueleto@ on
|
||||||
<https://github.com/meteficha/esqueleto/>.
|
<https://github.com/meteficha/esqueleto/>.
|
||||||
|
.
|
||||||
|
The name of this library means \"skeleton\" in Portuguese and
|
||||||
|
contains all three SQL letters in the correct order =).
|
||||||
|
|
||||||
source-repository head
|
source-repository head
|
||||||
type: git
|
type: git
|
||||||
|
|||||||
@ -1,21 +1,23 @@
|
|||||||
{-# LANGUAGE FlexibleContexts, FlexibleInstances, GADTs #-}
|
{-# LANGUAGE FlexibleContexts, FlexibleInstances, GADTs #-}
|
||||||
-- | Main module of @esqueleto@. This module replaces
|
-- | The @esqueleto@ EDSL (embedded domain specific language).
|
||||||
-- @Database.Persist@, so instead of importing that module you
|
-- This module replaces @Database.Persist@, so instead of
|
||||||
-- should just import this one:
|
-- importing that module you should just import this one:
|
||||||
--
|
--
|
||||||
-- @
|
-- @
|
||||||
-- import Database.Esqueleto
|
-- import Database.Esqueleto
|
||||||
-- @
|
|
||||||
--
|
|
||||||
-- If you still have legacy code using @Database.Persist.Query@
|
|
||||||
-- (which is exported by @Database.Persist@), you may import it
|
|
||||||
-- qualified:
|
|
||||||
--
|
|
||||||
-- @
|
|
||||||
-- import qualified Database.Persist.Query as OldQuery
|
-- import qualified Database.Persist.Query as OldQuery
|
||||||
-- @
|
-- @
|
||||||
module Database.Esqueleto
|
module Database.Esqueleto
|
||||||
( -- * @esqueleto@'s Language
|
( -- * Setup
|
||||||
|
-- $setup
|
||||||
|
|
||||||
|
-- * Introduction
|
||||||
|
-- $introduction
|
||||||
|
|
||||||
|
-- * Getting started
|
||||||
|
-- $gettingstarted
|
||||||
|
|
||||||
|
-- * @esqueleto@'s Language
|
||||||
Esqueleto( where_, on, orderBy, asc, desc
|
Esqueleto( where_, on, orderBy, asc, desc
|
||||||
, sub_select, sub_selectDistinct, (^.), (?.)
|
, sub_select, sub_selectDistinct, (^.), (?.)
|
||||||
, val, isNothing, just, nothing, countRows, not_
|
, val, isNothing, just, nothing, countRows, not_
|
||||||
@ -55,6 +57,232 @@ import Database.Persist.Store hiding (delete)
|
|||||||
import Database.Persist.GenericSql
|
import Database.Persist.GenericSql
|
||||||
import qualified Database.Persist.Store
|
import qualified Database.Persist.Store
|
||||||
|
|
||||||
|
-- $setup
|
||||||
|
--
|
||||||
|
-- If you're already using @persistent@, then you're ready to use
|
||||||
|
-- @esqueleto@, no further setup is needed. If you're just
|
||||||
|
-- starting a new project and would like to use @esqueleto@, take
|
||||||
|
-- a look at @persistent@'s book first
|
||||||
|
-- (<http://www.yesodweb.com/book/persistent>) to learn how to
|
||||||
|
-- define your schema.
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- $introduction
|
||||||
|
--
|
||||||
|
-- The main goals of @esqueleto@ are to:
|
||||||
|
--
|
||||||
|
-- * Be easily translatable to SQL. When you take a look at a
|
||||||
|
-- @esqueleto@ query, you should be able to know exactly how
|
||||||
|
-- the SQL query will end up. (As opposed to being a
|
||||||
|
-- relational algebra EDSL such as HaskellDB, which is
|
||||||
|
-- non-trivial to translate into SQL.)
|
||||||
|
--
|
||||||
|
-- * Support the mostly used SQL features. We'd like you to be
|
||||||
|
-- able to use @esqueleto@ for all of your queries, no
|
||||||
|
-- exceptions. Send a pull request or open an issue on our
|
||||||
|
-- project page (<https://github.com/meteficha/esqueleto>) if
|
||||||
|
-- there's anything missing that you'd like to see.
|
||||||
|
--
|
||||||
|
-- * Be as type-safe as possible. There are ways of shooting
|
||||||
|
-- yourself in the foot while using @esqueleto@ because it's
|
||||||
|
-- extremely hard to provide 100% type-safety into a SQL-like
|
||||||
|
-- EDSL---there's a tension between supporting features with a
|
||||||
|
-- nice syntax and rejecting bad code. However, we strive to
|
||||||
|
-- provide as many type checks as possible. If you get bitten
|
||||||
|
-- by some invalid code that type-checks, please open an issue
|
||||||
|
-- on our project page so we can take a look.
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- $gettingstarted
|
||||||
|
--
|
||||||
|
-- We like clean, easy-to-read EDSLs. However, in order to
|
||||||
|
-- achieve this goal we've used a lot of type hackery, leading to
|
||||||
|
-- some hard-to-read type signatures. On this section, we'll try
|
||||||
|
-- to build some intuition about the syntax.
|
||||||
|
--
|
||||||
|
-- For the following examples, we'll use this example schema:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- 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
|
||||||
|
-- |]
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- Most of @esqueleto@ was created with @SELECT@ statements in
|
||||||
|
-- mind, not only because they're the most common but also
|
||||||
|
-- because they're the most complex kind of statement. The most
|
||||||
|
-- simple kind of @SELECT@ would be:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- SELECT *
|
||||||
|
-- FROM Person
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- In @esqueleto@, we may write the same query above as:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- do people <- 'select' $
|
||||||
|
-- 'from' $ \\person -> do
|
||||||
|
-- return person
|
||||||
|
-- liftIO $ mapM_ (putStrLn . personName . entityVal) people
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- The expression above has type @SqlPersist m ()@, while
|
||||||
|
-- @people@ has type @[Entity Person]@. The query above will be
|
||||||
|
-- translated into exactly the same query we wrote manually, but
|
||||||
|
-- instead of @SELECT *@ it will list all entity fields (using
|
||||||
|
-- @*@ is not robust). Note that @esqueleto@ knows that we want
|
||||||
|
-- an @Entity Person@ just because of the @personName@ that we're
|
||||||
|
-- printing later.
|
||||||
|
--
|
||||||
|
-- However, most of the time we need to filter our queries using
|
||||||
|
-- @WHERE@. For example:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- SELECT *
|
||||||
|
-- FROM Person
|
||||||
|
-- WHERE Person.name = \"John\"
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- In @esqueleto@, we may write the same query above as:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- select $
|
||||||
|
-- from $ \\p -> do
|
||||||
|
-- 'where_' (p '^.' PersonName '==.' 'val' \"John\")
|
||||||
|
-- return p
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- Although @esqueleto@'s code is a bit more noisy, it's has
|
||||||
|
-- almost the same structure (save from the @return@). The
|
||||||
|
-- @('^.')@ operator is used to project a field from an entity.
|
||||||
|
-- The field name is the same one generated by @persistent@'s
|
||||||
|
-- Template Haskell functions. We use 'val' to lift a constant
|
||||||
|
-- Haskell value into the SQL query.
|
||||||
|
--
|
||||||
|
-- Another example would be:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- SELECT *
|
||||||
|
-- FROM Person
|
||||||
|
-- WHERE Person.age >= 18
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- In @esqueleto@, we may write the same query above as:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- select $
|
||||||
|
-- from $ \\p -> do
|
||||||
|
-- where_ (p ^. PersonAge '>=.' 'just' (val 18))
|
||||||
|
-- return p
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- Since @age@ is an optional @Person@ field, we use 'just' lift
|
||||||
|
-- @val 18 :: SqlExpr (Single Int)@ into @just (val 18) ::
|
||||||
|
-- SqlExpr (Single (Just Int))@.
|
||||||
|
--
|
||||||
|
-- Implicit joins are represented by tuples. For example, to get
|
||||||
|
-- the list of all blog posts and their authors, we could write:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- SELECT BlogPost.*, Person.*
|
||||||
|
-- FROM BlogPost, Person
|
||||||
|
-- WHERE BlogPost.authorId = Person.id
|
||||||
|
-- ORDER BY BlogPost.title ASC
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- In @esqueleto@, we may write the same query above as:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- select $
|
||||||
|
-- from $ \\(b, p) -> do
|
||||||
|
-- where_ (b ^. BlogPostAuthorId ==. p ^. PersonId)
|
||||||
|
-- 'orderBy' ['asc' (b ^. BlogPostTitle)]
|
||||||
|
-- return (b, p)
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- However, we may want your results to include people who don't
|
||||||
|
-- have any blog posts as well using a @LEFT OUTER JOIN@:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- SELECT Person.*, BlogPost.*
|
||||||
|
-- FROM Person LEFT OUTER JOIN BlogPost
|
||||||
|
-- ON Person.id = BlogPost.authorId
|
||||||
|
-- ORDER BY Person.name ASC, BlogPost.title ASC
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- In @esqueleto@, we may write the same query above as:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- select $
|
||||||
|
-- from $ \\(p ``LeftOuterJoin`` mb) -> do
|
||||||
|
-- 'on' (just (p ^. PersonId) ==. mb '?.' BlogPostAuthorId)
|
||||||
|
-- orderBy [asc (p ^. PersonName), asc (mb '?.' BlogPostTitle)]
|
||||||
|
-- return (p, mb)
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- On a @LEFT OUTER JOIN@ the entity on the right hand side may
|
||||||
|
-- not exist (i.e. there may be a @Person@ without any
|
||||||
|
-- @BlogPost@s), so while @p :: SqlExpr (Entity Person)@, we have
|
||||||
|
-- @mb :: SqlExpr (Maybe (Entity BlogPost))@. The whole
|
||||||
|
-- expression above has type @SqlPersist m [(Entity Person, Maybe
|
||||||
|
-- (Entity BlogPost))]@. Instead of using @(^.)@, we used
|
||||||
|
-- @('?.')@ to project a field from a @Maybe (Entity a)@.
|
||||||
|
--
|
||||||
|
-- We are by no means limited to joins of two tables, nor by
|
||||||
|
-- joins of different tables. For example, we may want a list
|
||||||
|
-- the @Follow@ entity:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- SELECT P1.*, Follow.*, P2.*
|
||||||
|
-- FROM Person AS P1
|
||||||
|
-- INNER JOIN Follow ON P1.id = Follow.follower
|
||||||
|
-- INNER JOIN P2 ON P2.id = Follow.followed
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- In @esqueleto@, we may write the same query above as:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- select $
|
||||||
|
-- from $ \\(p1 ``InnerJoin`` f ``InnerJoin`` p2) -> do
|
||||||
|
-- on (p2 ^. PersonId ==. f ^. FollowFollowed)
|
||||||
|
-- on (p1 ^. PersonId ==. f ^. FollowFollower)
|
||||||
|
-- return (p1, f, p2)
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- /Note carefully that the order of the ON clauses is/
|
||||||
|
-- /reversed!/ You're required to write your 'on's in reverse
|
||||||
|
-- order because that helps composability (see the documention of
|
||||||
|
-- 'on' for more details).
|
||||||
|
--
|
||||||
|
-- We also currently supports @UPDATE@ and @DELETE@ statements.
|
||||||
|
-- For example:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- do 'update' $ \\p -> do
|
||||||
|
-- 'set' p [ PersonName '=.' val \"João\" ]
|
||||||
|
-- where_ (p ^. PersonName ==. val \"Joao\")
|
||||||
|
-- 'delete' $
|
||||||
|
-- from $ \\p -> do
|
||||||
|
-- where_ (p ^. PersonAge <. just (val 14))
|
||||||
|
-- @
|
||||||
|
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
-- | Synonym for 'Database.Persist.Store.delete' that does not
|
-- | Synonym for 'Database.Persist.Store.delete' that does not
|
||||||
-- clash with @esqueleto@'s 'delete'.
|
-- clash with @esqueleto@'s 'delete'.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user