diff --git a/esqueleto.cabal b/esqueleto.cabal
index 921472e..57dad3b 100644
--- a/esqueleto.cabal
+++ b/esqueleto.cabal
@@ -38,6 +38,9 @@ description:
functions), so please open an issue or send a pull request if
you need anything that is not covered by @esqueleto@ on
.
+ .
+ The name of this library means \"skeleton\" in Portuguese and
+ contains all three SQL letters in the correct order =).
source-repository head
type: git
diff --git a/src/Database/Esqueleto.hs b/src/Database/Esqueleto.hs
index 8e55ca7..1cb488e 100644
--- a/src/Database/Esqueleto.hs
+++ b/src/Database/Esqueleto.hs
@@ -1,21 +1,23 @@
{-# LANGUAGE FlexibleContexts, FlexibleInstances, GADTs #-}
--- | Main module of @esqueleto@. This module replaces
--- @Database.Persist@, so instead of importing that module you
--- should just import this one:
+-- | The @esqueleto@ EDSL (embedded domain specific language).
+-- This module replaces @Database.Persist@, so instead of
+-- importing that module you should just import this one:
--
-- @
-- 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
-- @
module Database.Esqueleto
- ( -- * @esqueleto@'s Language
+ ( -- * Setup
+ -- $setup
+
+ -- * Introduction
+ -- $introduction
+
+ -- * Getting started
+ -- $gettingstarted
+
+ -- * @esqueleto@'s Language
Esqueleto( where_, on, orderBy, asc, desc
, sub_select, sub_selectDistinct, (^.), (?.)
, val, isNothing, just, nothing, countRows, not_
@@ -55,6 +57,232 @@ import Database.Persist.Store hiding (delete)
import Database.Persist.GenericSql
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
+-- () 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 () 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
-- clash with @esqueleto@'s 'delete'.