Bump version and add more comments

This commit is contained in:
belevy 2021-02-11 20:41:21 -06:00
parent ae9ef126d9
commit 4f9793f6cb
8 changed files with 239 additions and 70 deletions

View File

@ -1,14 +1,17 @@
3.4.1.0
3.5.0.0
=======-
- @belevy
- [#228](https://github.com/bitemyapp/esqueleto/pull/228)
- Destroy all GADTs; Removes the From GADT and SqlExpr GADT
- From GADT is replaced with a From typeclass
- From GADT is replaced with a From data type and FromRaw
- SqlExpr is now all defined in terms of ERaw
- Modified ERaw to contain a SqlExprMeta with any extra information
that may be needed
- Experimental top level is now strictly for documentation and all the
implementation details are in Experimental.* modules
3.4.1.0
=======
- @Vlix
- [#232](https://github.com/bitemyapp/esqueleto/pull/232)
- Export the `ValidOnClauseValue` type family

View File

@ -1,7 +1,7 @@
cabal-version: 1.12
name: esqueleto
version: 3.4.1.0
version: 3.5.0.0
synopsis: Type-safe EDSL for SQL queries on persistent backends.
description: @esqueleto@ is a bare bones, type-safe EDSL for SQL queries that works with unmodified @persistent@ SQL backends. Its language closely resembles SQL, so you don't have to learn new concepts, just new syntax, and it's fairly easy to predict the generated SQL and optimize it for your backend. Most kinds of errors committed when writing SQL are caught as compile-time errors---although it is possible to write type-checked @esqueleto@ queries that fail at runtime.
.

View File

@ -18,13 +18,24 @@ module Database.Esqueleto.Experimental
-- * Documentation
Table(..)
-- ** Basic Queries
from
, table
, from
, Table(..)
, SubQuery(..)
, selectQuery
-- ** Joins
, (:&)(..)
, on
, innerJoin
, innerJoinLateral
, leftJoin
, leftJoinLateral
, rightJoin
, fullOuterJoin
, crossJoin
, crossJoinLateral
-- ** Set Operations
-- $sql-set-operations
@ -42,16 +53,7 @@ module Database.Esqueleto.Experimental
, with
, withRecursive
, innerJoin
, innerJoinLateral
, leftJoin
, leftJoinLateral
, rightJoin
, fullOuterJoin
, crossJoin
, crossJoinLateral
-- * Internals
-- ** Internals
, From(..)
, ToMaybe(..)
, ToAlias(..)
@ -60,8 +62,8 @@ module Database.Esqueleto.Experimental
, ToAliasReferenceT
, ToSqlSetOperation(..)
, ValidOnClauseValue
-- * The Normal Stuff
-- * The Normal Stuff
, where_
, groupBy
, orderBy
@ -174,6 +176,7 @@ module Database.Esqueleto.Experimental
, DistinctOn
, LockingKind(..)
, SqlString
-- ** Joins
, InnerJoin(..)
, CrossJoin(..)
@ -182,7 +185,8 @@ module Database.Esqueleto.Experimental
, FullOuterJoin(..)
, JoinKind(..)
, OnClauseWithoutMatchingJoinException(..)
-- * SQL backend
-- ** SQL backend
, SqlQuery
, SqlExpr
, SqlEntity
@ -196,22 +200,20 @@ module Database.Esqueleto.Experimental
, insertSelectCount
, (<#)
, (<&>)
-- ** Rendering Queries
, renderQueryToText
, renderQuerySelect
, renderQueryUpdate
, renderQueryDelete
, renderQueryInsertInto
-- * Internal.Language
-- * RDBMS-specific modules
-- $rdbmsSpecificModules
-- * Helpers
-- ** Helpers
, valkey
, valJ
, associateJoin
-- * Re-exports
-- ** Re-exports
-- $reexports
, deleteKey
, module Database.Esqueleto.Internal.PersistentImport
@ -316,7 +318,7 @@ import Database.Esqueleto.Experimental.ToMaybe
--
-- @
-- select $ do
-- people <- from $ Table \@Person
-- people <- from $ table \@Person
-- where_ (people ^. PersonName ==. val \"John\")
-- pure people
-- @
@ -344,8 +346,8 @@ import Database.Esqueleto.Experimental.ToMaybe
-- @
-- select $ do
-- (people :& blogPosts) <-
-- from $ Table \@Person
-- \`LeftOuterJoin\` Table \@BlogPost
-- from $ table \@Person
-- \`leftJoin\` table \@BlogPost
-- \`on\` (\\(people :& blogPosts) ->
-- people ^. PersonId ==. blogPosts ?. BlogPostAuthorId)
-- where_ (people ^. PersonAge >. val 18)
@ -376,7 +378,7 @@ import Database.Esqueleto.Experimental.ToMaybe
--
-- 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
-- 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 'JOIN's.
@ -384,11 +386,11 @@ import Database.Esqueleto.Experimental.ToMaybe
-- @
-- select $ do
-- (people1 :& followers :& people2) <-
-- from $ Table \@Person
-- \`InnerJoin` Table \@Follow
-- from $ table \@Person
-- \`innerJoin` table \@Follow
-- \`on\` (\\(people1 :& followers) ->
-- people1 ^. PersonId ==. followers ^. FollowFollowed)
-- \`InnerJoin` Table \@Person
-- \`innerJoin` table \@Person
-- \`on\` (\\(_ :& followers :& people2) ->
-- followers ^. FollowFollower ==. people2 ^. PersonId)
-- where_ (people1 ^. PersonName ==. val \"John\")
@ -420,8 +422,8 @@ import Database.Esqueleto.Experimental.ToMaybe
-- peopleWithPosts <-
-- from $ do
-- (people :& blogPosts) <-
-- from $ Table \@Person
-- \`InnerJoin\` Table \@BlogPost
-- from $ table \@Person
-- \`innerJoin\` table \@BlogPost
-- \`on\` (\\(p :& bP) ->
-- p ^. PersonId ==. bP ^. BlogPostAuthorId)
-- groupBy (people ^. PersonId)
@ -451,8 +453,8 @@ import Database.Esqueleto.Experimental.ToMaybe
-- (authors, blogPosts) <- from $
-- (do
-- (author :& blogPost) <-
-- from $ Table \@Person
-- \`InnerJoin\` Table \@BlogPost
-- from $ table \@Person
-- \`innerJoin\` table \@BlogPost
-- \`on\` (\\(a :& bP) ->
-- a ^. PersonId ==. bP ^. BlogPostAuthorId)
-- where_ (author ^. PersonId ==. val currentPersonId)
@ -461,11 +463,11 @@ import Database.Esqueleto.Experimental.ToMaybe
-- \`union_\`
-- (do
-- (follow :& blogPost :& author) <-
-- from $ Table \@Follow
-- \`InnerJoin\` Table \@BlogPost
-- from $ table \@Follow
-- \`innerJoin\` table \@BlogPost
-- \`on\` (\\(f :& bP) ->
-- f ^. FollowFollowed ==. bP ^. BlogPostAuthorId)
-- \`InnerJoin\` Table \@Person
-- \`innerJoin\` table \@Person
-- \`on\` (\\(_ :& bP :& a) ->
-- bP ^. BlogPostAuthorId ==. a ^. PersonId)
-- where_ (follow ^. FollowFollower ==. val currentPersonId)
@ -484,14 +486,14 @@ import Database.Esqueleto.Experimental.ToMaybe
-- @
-- select $ do
-- (salesPerson :& maxSaleAmount :& maxSaleCustomerName) <-
-- from $ Table \@SalesPerson
-- \`CrossJoin\` (\\salesPerson -> do
-- sales <- from $ Table \@Sale
-- from $ table \@SalesPerson
-- \`crossJoinLateral\` (\\salesPerson -> do
-- sales <- from $ table \@Sale
-- where_ $ sales ^. SaleSalesPersonId ==. salesPerson ^. SalesPersonId
-- pure $ max_ (sales ^. SaleAmount)
-- )
-- \`CrossJoin\` (\\(salesPerson :& maxSaleAmount) -> do
-- sales <- from $ Table \@Sale
-- \`crossJoinLateral\` (\\(salesPerson :& maxSaleAmount) -> do
-- sales <- from $ table \@Sale
-- where_ $ sales ^. SaleSalesPersonId ==. salesPerson ^. SalesPersonId
-- &&. sales ^. SaleAmount ==. maxSaleAmount
-- pure $ sales ^. SaleCustomerName)
@ -539,12 +541,12 @@ import Database.Esqueleto.Experimental.ToMaybe
-- @
-- select $ from $
-- (do
-- a <- from Table @A
-- a <- from $ table @A
-- pure $ a ^. ASomeCol
-- )
-- \`union_\`
-- (do
-- b <- from Table @B
-- b <- from $ table @B
-- pure $ b ^. BSomeCol
-- )
-- @

View File

@ -30,11 +30,11 @@ import Database.Esqueleto.Internal.PersistentImport
-- | 'FROM' clause, used to bring entities into scope.
--
-- Internally, this function uses the `From` datatype and the
-- `From` typeclass. 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
-- 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@).
from :: ToFrom a a' => a -> SqlQuery a'
@ -44,23 +44,44 @@ from f = do
pure a
type RawFn = NeedParens -> IdentInfo -> (TLB.Builder, [PersistValue])
-- | 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@
--
-- /Since: 3.5.0.0/
newtype From a = From
{ unFrom :: SqlQuery (a, RawFn)}
-- | A helper class primarily designed to allow using @SqlQuery@ directly in
-- a From expression. This is also useful for embedding a @SqlSetOperation@,
-- as well as supporting backwards compatibility for the
-- data constructor join tree used prior to /3.5.0.0/
--
-- /Since: 3.5.0.0/
class ToFrom a r | a -> r where
toFrom :: a -> From r
instance ToFrom (From a) a where
toFrom = id
-- | Data type for bringing a Table into scope in a JOIN tree
--
-- @
-- select $ from $ Table \@People
-- @
{-# DEPRECATED Table "/Since: 3.5.0.0/ - use 'table' instead" #-}
data Table a = Table
instance PersistEntity ent => ToFrom (Table ent) (SqlExpr (Entity ent)) where
toFrom _ = table
-- | Bring a PersistEntity into scope from a table
--
-- @
-- select $ from $ table \@People
-- @
--
-- /Since: 3.5.0.0/
table :: forall ent. PersistEntity ent => From (SqlExpr (Entity ent))
table = From $ do
let ed = entityDef (Proxy @ent)
@ -85,6 +106,21 @@ instance (SqlSelect a r, ToAlias a, ToAliasReference a) => ToFrom (SubQuery (Sql
instance (SqlSelect a r, ToAlias a, ToAliasReference a) => ToFrom (SqlQuery a) a where
toFrom = selectQuery
-- | 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 ]
-- ...
-- @
--
-- /Since: 3.5.0.0/
selectQuery :: (SqlSelect a r, ToAlias a, ToAliasReference a) => SqlQuery a -> From a
selectQuery subquery = From $ do
-- We want to update the IdentState without writing the query to side data

View File

@ -56,21 +56,21 @@ with query = do
-- @
-- select $ do
-- cte <- withRecursive
-- (do $
-- person <- from $ Table \@Person
-- (do
-- person <- from $ table \@Person
-- where_ $ person ^. PersonId ==. val personId
-- pure person
-- )
-- unionAll_
-- (\\self -> do $
-- (\\self -> do
-- (p :& f :& p2 :& pSelf) <- from self
-- \`InnerJoin\` $ Table \@Follow
-- \`innerJoin\` $ table \@Follow
-- \`on\` (\\(p :& f) ->
-- p ^. PersonId ==. f ^. FollowFollower)
-- \`InnerJoin\` $ Table \@Person
-- \`innerJoin\` $ table \@Person
-- \`on\` (\\(p :& f :& p2) ->
-- f ^. FollowFollowed ==. p2 ^. PersonId)
-- \`LeftOuterJoin\` self
-- \`leftJoin\` self
-- \`on\` (\\(_ :& _ :& p2 :& pSelf) ->
-- just (p2 ^. PersonId) ==. pSelf ?. PersonId)
-- where_ $ isNothing (pSelf ?. PersonId)

View File

@ -65,8 +65,8 @@ type family ValidOnClauseValue a :: Constraint where
--
-- @
-- select $
-- from $ Table \@Person
-- \`InnerJoin\` Table \@BlogPost
-- from $ table \@Person
-- \`innerJoin\` table \@BlogPost
-- \`on\` (\\(p :& bP) ->
-- p ^. PersonId ==. bP ^. BlogPostAuthorId)
-- @
@ -103,6 +103,19 @@ type family HasOnClause actual expected :: Constraint where
)
-- | INNER JOIN
--
-- Used as an infix operator \`innerJoin\`
--
-- @
-- select $
-- from $ table \@Person
-- \`innerJoin\` table \@BlogPost
-- \`on\` (\\(p :& bp) ->
-- p ^. PersonId ==. bp ^. BlogPostAuthorId)
-- @
--
-- /Since: 3.5.0.0/
innerJoin :: ( ToFrom a a'
, ToFrom b b'
, HasOnClause rhs (a' :& b')
@ -115,6 +128,16 @@ innerJoin lhs (rhs, on') = From $ do
pure $ (ret, fromJoin " INNER JOIN " leftFrom rightFrom (Just $ on' ret))
-- | INNER JOIN LATERAL
--
-- 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
--
-- Used as an infix operator \`innerJoinLateral\`
--
-- See example 6
--
-- /Since: 3.5.0.0/
innerJoinLateral :: ( ToFrom a a'
, HasOnClause rhs (a' :& b)
, SqlSelect b r
@ -129,6 +152,17 @@ innerJoinLateral lhs (rhsFn, on') = From $ do
let ret = leftVal :& rightVal
pure $ (ret, fromJoin " INNER JOIN LATERAL " leftFrom rightFrom (Just $ on' ret))
-- | CROSS JOIN
--
-- Used as an infix \`crossJoin\`
--
-- @
-- select $ do
-- from $ table \@Person
-- \`crossJoin\` table \@BlogPost
-- @
--
-- /Since: 3.5.0.0/
crossJoin :: ( ToFrom a a'
, ToFrom b b'
) => a -> b -> From (a' :& b')
@ -138,6 +172,16 @@ crossJoin lhs rhs = From $ do
let ret = leftVal :& rightVal
pure $ (ret, fromJoin " CROSS JOIN " leftFrom rightFrom Nothing)
-- | CROSS JOIN LATERAL
--
-- 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
--
-- /Since: 3.5.0.0/
crossJoinLateral :: ( ToFrom a a'
, SqlSelect b r
, ToAlias b
@ -150,6 +194,23 @@ crossJoinLateral lhs rhsFn = From $ do
let ret = leftVal :& rightVal
pure $ (ret, fromJoin " CROSS JOIN LATERAL " leftFrom rightFrom Nothing)
-- | LEFT OUTER JOIN
--
-- 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) ->
-- p ^. PersonId ==. bp ?. BlogPostAuthorId)
-- @
--
-- /Since: 3.5.0.0/
leftJoin :: ( ToFrom a a'
, ToFrom b b'
, ToMaybe b'
@ -162,6 +223,18 @@ leftJoin lhs (rhs, on') = From $ do
let ret = leftVal :& toMaybe rightVal
pure $ (ret, fromJoin " LEFT OUTER JOIN " leftFrom rightFrom (Just $ on' ret))
-- | LEFT OUTER JOIN LATERAL
--
-- 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
--
-- Used as an infix operator \`leftJoinLateral\`
--
-- See example 6 for how to use LATERAL
--
-- /Since: 3.5.0.0/
leftJoinLateral :: ( ToFrom a a'
, SqlSelect b r
, HasOnClause rhs (a' :& ToMaybeT b)
@ -177,6 +250,23 @@ leftJoinLateral lhs (rhsFn, on') = From $ do
let ret = leftVal :& toMaybe rightVal
pure $ (ret, fromJoin " LEFT OUTER JOIN LATERAL " leftFrom rightFrom (Just $ on' ret))
-- | RIGHT OUTER JOIN
--
-- 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)
-- @
--
-- /Since: 3.5.0.0/
rightJoin :: ( ToFrom a a'
, ToFrom b b'
, ToMaybe a'
@ -189,6 +279,22 @@ rightJoin lhs (rhs, on') = From $ do
let ret = toMaybe leftVal :& rightVal
pure $ (ret, fromJoin " RIGHT OUTER JOIN " leftFrom rightFrom (Just $ on' ret))
-- | FULL OUTER JOIN
--
-- Join where both sides of the join may not exist.
-- Because of this the result needs to be handled as a Maybe
--
-- Used as an infix operator \`fullOuterJoin\`
--
-- @
-- select $
-- from $ table \@Person
-- \`fullOuterJoin\` table \@BlogPost
-- \`on\` (\\(p :& bp) ->
-- p ?. PersonId ==. bp ?. BlogPostAuthorId)
-- @
--
-- /Since: 3.5.0.0/
fullOuterJoin :: ( ToFrom a a'
, ToFrom b b'
, ToMaybe a'

View File

@ -24,6 +24,12 @@ import Database.Esqueleto.Internal.Internal hiding (From(..), from, on)
import Database.Esqueleto.Internal.PersistentImport
(DBName(..), Entity, PersistEntity, PersistValue)
-- | Data type used to implement the SqlSetOperation language
-- this type is implemented in the same way as a @From@
--
-- Semantically a @SqlSetOperation@ is always a @From@ but not vice versa
--
-- /Since: 3.5.0.0/
newtype SqlSetOperation a = SqlSetOperation
{ unSqlSetOperation :: NeedParens -> SqlQuery (a, IdentInfo -> (TLB.Builder, [PersistValue]))}
@ -34,6 +40,9 @@ instance ToAliasReference a => ToFrom (SqlSetOperation a) a where
ref <- toAliasReference ident a
pure (ref, \_ info -> (first parens $ fromClause info) <> (" AS " <> useIdent info ident, mempty))
-- | Type class to support direct use of @SqlQuery@ in a set operation tree
--
-- /Since: 3.5.0.0/
class ToSqlSetOperation a r | a -> r where
toSqlSetOperation :: a -> SqlSetOperation r
instance ToSqlSetOperation (SqlSetOperation a) a where
@ -57,7 +66,10 @@ instance (SqlSelect a r, ToAlias a, ToAliasReference a) => ToSqlSetOperation (Sq
Never
pure (aliasedValue, \info -> first (parensM p') $ toRawSql SELECT info aliasedQuery)
mkSetOperation :: (ToSqlSetOperation a a', ToSqlSetOperation b a') => TLB.Builder -> a -> b -> SqlSetOperation a'
-- | Helper function for defining set operations
-- /Since: 3.5.0.0/
mkSetOperation :: (ToSqlSetOperation a a', ToSqlSetOperation b a')
=> TLB.Builder -> a -> b -> SqlSetOperation a'
mkSetOperation operation lhs rhs = SqlSetOperation $ \p -> do
(leftValue, leftClause) <- unSqlSetOperation (toSqlSetOperation lhs) p
(_, rightClause) <- unSqlSetOperation (toSqlSetOperation rhs) p
@ -68,7 +80,10 @@ data Union a b = a `Union` b
instance ToSqlSetOperation a a' => ToSqlSetOperation (Union a a) a' where
toSqlSetOperation (Union a b) = union_ a b
-- | Overloaded @union_@ function to support use in both 'SqlSetOperation'
-- and 'withRecursive'
--
-- /Since: 3.5.0.0/
class Union_ a where
-- | @UNION@ SQL set operation. Can be used as an infix function between 'SqlQuery' values.
union_ :: a
@ -77,6 +92,10 @@ instance (ToSqlSetOperation a c, ToSqlSetOperation b c, res ~ SqlSetOperation c)
=> Union_ (a -> b -> res) where
union_ = mkSetOperation " UNION "
-- | Overloaded @unionAll_@ function to support use in both 'SqlSetOperation'
-- and 'withRecursive'
--
-- /Since: 3.5.0.0/
class UnionAll_ a where
-- | @UNION@ @ALL@ SQL set operation. Can be used as an infix function between 'SqlQuery' values.
unionAll_ :: a

View File

@ -2035,6 +2035,8 @@ data SqlExprMeta = SqlExprMeta
, sqlExprMetaIsReference :: Bool -- Is this SqlExpr a reference to the selected value/entity (supports subqueries)
}
-- | Empty 'SqlExprMeta' if you are constructing an 'ERaw' probably use this
-- for your meta
noMeta :: SqlExprMeta
noMeta = SqlExprMeta
{ sqlExprMetaCompositeFields = Nothing
@ -2042,18 +2044,19 @@ noMeta = SqlExprMeta
, sqlExprMetaIsReference = False
}
-- | Does this meta contain values for composite fields.
-- This field is field out for composite key values
hasCompositeKeyMeta :: SqlExprMeta -> Bool
hasCompositeKeyMeta = Maybe.isJust . sqlExprMetaCompositeFields
-- | An expression on the SQL backend.
--
-- There are many comments describing the constructors of this
-- data type. However, Haddock doesn't like GADTs, so you'll have to read them by hitting \"Source\".
-- Raw expression: states whether parenthesis are needed
-- around this expression, and takes information about the SQL
-- connection (mainly for escaping names) and returns both an
-- string ('TLB.Builder') and a list of values to be
-- interpolated by the SQL backend.
-- 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 ('TLB.Builder') and a list of values to be
-- interpolated by the SQL backend.
data SqlExpr a = ERaw SqlExprMeta (NeedParens -> IdentInfo -> (TLB.Builder, [PersistValue]))
-- | Data type to support from hack