esqueleto 0.2.1 → 0.2.2
raw patch · 3 files changed
+178/−35 lines, 3 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Database.Esqueleto.Internal.Language: class Esqueleto query expr backend => From query expr backend a
+ Database.Esqueleto.Internal.Language: class Esqueleto query expr backend => FromPreprocess query expr backend a
+ Database.Esqueleto.Internal.Sql: class SqlSelect a r | a -> r, r -> a
+ Database.Esqueleto.Internal.Sql: type Escape = DBName -> Builder
Files
- esqueleto.cabal +3/−2
- src/Database/Esqueleto/Internal/Language.hs +89/−26
- src/Database/Esqueleto/Internal/Sql.hs +86/−7
esqueleto.cabal view
@@ -1,5 +1,5 @@ name: esqueleto-version: 0.2.1+version: 0.2.2 synopsis: Bare bones, type-safe EDSL for SQL queries on persistent backends. homepage: https://github.com/meteficha/esqueleto license: BSD3@@ -42,7 +42,8 @@ <https://github.com/meteficha/esqueleto/>. . The name of this library means \"skeleton\" in Portuguese and- contains all three SQL letters in the correct order =).+ contains all three SQL letters in the correct order =). It was+ inspired by Scala's Squeryl but created from scratch. source-repository head type: git
src/Database/Esqueleto/Internal/Language.hs view
@@ -7,8 +7,12 @@ , TypeFamilies , UndecidableInstances #-}+-- | This is an internal module, anything exported by this module+-- may change without a major version bump. Please use only+-- "Database.Esqueleto" if possible. module Database.Esqueleto.Internal.Language- ( Esqueleto(..)+ ( -- * The pretty face+ Esqueleto(..) , from , Value(..) , InnerJoin(..)@@ -16,12 +20,15 @@ , LeftOuterJoin(..) , RightOuterJoin(..) , FullOuterJoin(..)- , JoinKind(..)- , IsJoinKind(..) , OnClauseWithoutMatchingJoinException(..)- , PreprocessedFrom , OrderBy , Update+ -- * The guts+ , JoinKind(..)+ , IsJoinKind(..)+ , PreprocessedFrom+ , From+ , FromPreprocess ) where import Control.Applicative (Applicative(..), (<$>))@@ -82,7 +89,7 @@ -- -- @ -- select $- -- from $ \(foo `InnerJoin` bar) -> do+ -- from $ \\(foo ``InnerJoin`` bar) -> do -- on (foo ^. FooId ==. bar ^. BarFooId) -- ... -- @@@ -94,7 +101,7 @@ -- -- @ -- select $- -- from $ \(foo `InnerJoin` bar `InnerJoin` baz) -> do+ -- from $ \\(foo ``InnerJoin`` bar ``InnerJoin`` baz) -> do -- on (baz ^. BazId ==. bar ^. BarBazId) -- on (foo ^. FooId ==. bar ^. BarFooId) -- ...@@ -105,18 +112,16 @@ -- -- @ -- let query1 =- -- from $ \(foo `InnerJoin` bar) -> do+ -- from $ \\(foo ``InnerJoin`` bar) -> do -- on (foo ^. FooId ==. bar ^. BarFooId)- -- -- query2 =- -- from $ \(mbaz `LeftOuterJoin` quux) -> do+ -- from $ \\(mbaz ``LeftOuterJoin`` quux) -> do -- return (mbaz ?. BazName, quux)- --- -- test1 = (,) <$> query1 <*> query2- -- test2 = flip (,) <$> query2 <*> query1+ -- test1 = (,) \<$\> query1 \<*\> query2+ -- test2 = flip (,) \<$\> query2 \<*\> query1 -- @ --- -- If the order was *not* reversed, then @test2@ would be+ -- If the order was /not/ reversed, then @test2@ would be -- broken: @query1@'s 'on' would refer to @query2@'s -- 'LeftOuterJoin'. on :: expr (Value Bool) -> query ()@@ -150,9 +155,9 @@ -- | @IS NULL@ comparison. isNothing :: PersistField typ => expr (Value (Maybe typ)) -> expr (Value Bool) - -- | Analog to 'Just', promotes a value of type @typ@ into one- -- of type @Maybe typ@. It should hold that @val . Just ===- -- just . val@.+ -- | Analogous to 'Just', promotes a value of type @typ@ into+ -- one of type @Maybe typ@. It should hold that @val . Just+ -- === just . val@. just :: expr (Value typ) -> expr (Value (Maybe typ)) -- | @NULL@ value.@@ -215,7 +220,7 @@ -- -- @ -- select $--- from $ \(person `LeftOuterJoin` pet) ->+-- from $ \\(person ``LeftOuterJoin`` pet) -> -- ... -- @ --@@ -226,6 +231,8 @@ -- FROM Person LEFT OUTER JOIN Pet -- ... -- @+--+-- See also: 'from'. data LeftOuterJoin a b = a `LeftOuterJoin` b -- | Data type that represents a @RIGHT OUTER JOIN@ (see 'LeftOuterJoin' for an example).@@ -290,19 +297,75 @@ data Update typ --- | @FROM@ clause: bring an entity into scope.+-- | @FROM@ clause: bring entities into scope. ----- The following types implement 'from':+-- This function internally uses two type classes in order to+-- provide some flexibility of how you may call it. Internally+-- we refer to these type classes as the two different magics. ----- * @Expr (Entity val)@, which brings a single entity into scope.+-- The innermost magic allows you to use @from@ with the+-- following types: ----- * Tuples of any other types supported by 'from'. Calling--- 'from' multiple times is the same as calling 'from' a--- single time and using a tuple.+-- * @expr (Entity val)@, which brings a single entity into+-- scope. ----- Note that using 'from' for the same entity twice does work--- and corresponds to a self-join. You don't even need to use--- two different calls to 'from', you may use a tuple.+-- * @expr (Maybe (Entity val))@, which brings a single entity+-- that may be @NULL@ into scope. Used for @OUTER JOIN@s.+--+-- * A @JOIN@ of any other two types allowed by the innermost+-- magic, where a @JOIN@ may be an 'InnerJoin', a 'CrossJoin', a+-- 'LeftOuterJoin', a 'RightOuterJoin', or a 'FullOuterJoin'.+-- The @JOINs@ have right fixity, the same as in SQL.+--+-- The outermost magic allows you to use @from@ on any tuples of+-- types supported by innermost magic (and also tuples of tuples,+-- and so on), up to 8-tuples.+--+-- Note that using @from@ for the same entity twice does work and+-- corresponds to a self-join. You don't even need to use two+-- different calls to @from@, you may use a @JOIN@ or a tuple.+--+-- The following are valid examples of uses of @from@ (the types+-- of the arguments of the lambda are inside square brackets):+--+-- @+-- from $ \\person -> ...+-- from $ \\(person, blogPost) -> ...+-- from $ \\(p ``LeftOuterJoin`` mb) -> ...+-- from $ \\(p1 ``InnerJoin`` f ``InnerJoin`` p2) -> ...+-- from $ \\((p1 ``InnerJoin`` f) ``InnerJoin`` p2) -> ...+-- @+--+-- The types of the arguments to the lambdas above are,+-- respectively:+--+-- @+-- person+-- :: ( Esqueleto query expr backend+-- , PersistEntity Person+-- , PersistEntityBackend Person ~ backend+-- ) => expr (Entity Person)+-- (person, blogPost)+-- :: (...) => (expr (Entity Person), expr (Entity BlogPost))+-- (p ``LeftOuterJoin`` mb)+-- :: (...) => InnerJoin (expr (Entity Person)) (expr (Maybe (Entity BlogPost)))+-- (p1 ``InnerJoin`` f ``InnerJoin`` p2)+-- :: (...) => InnerJoin+-- (expr (Entity Person))+-- (InnerJoin (expr (Entity Follow))+-- (expr (Entity Person)))+-- ((p1 ``InnerJoin`` f) ``InnerJoin`` p2) ::+-- :: (...) => InnerJoin+-- (InnerJoin (expr (Entity Person))+-- (expr (Entity Follow)))+-- (expr (Entity Person))+-- @+--+-- Note that some backends may not support all kinds of @JOIN@s.+-- For example, when using the SQL backend with SQLite, it will+-- not accept the last example above (which is associated to the+-- left, instead of being to the right) and will not accept+-- 'RightOuterJoin's or 'FullOuterJoin's. from :: From query expr backend a => (a -> query b) -> query b from = (from_ >>=)
src/Database/Esqueleto/Internal/Sql.hs view
@@ -7,20 +7,27 @@ , OverloadedStrings , UndecidableInstances #-}+-- | This is an internal module, anything exported by this module+-- may change without a major version bump. Please use only+-- "Database.Esqueleto" if possible. module Database.Esqueleto.Internal.Sql- ( SqlQuery+ ( -- * The pretty face+ SqlQuery , SqlExpr , select , selectSource , selectDistinct , selectDistinctSource+ , delete+ , update+ -- * The guts , rawSelectSource , runSource , rawExecute- , delete- , update , toRawSql , Mode(..)+ , Escape+ , SqlSelect ) where import Control.Applicative (Applicative(..), (<$>))@@ -199,6 +206,7 @@ data OrderByType = ASC | DESC +-- | (Internal) Backend-specific function that escapes a 'DBName'. type Escape = DBName -> TLB.Builder @@ -356,6 +364,45 @@ -- | Execute an @esqueleto@ @SELECT@ query inside @persistent@'s -- 'SqlPersist' 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 JOIN@s.+--+-- * 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+-- 'sub_select').+--+-- 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)@. select :: ( SqlSelect a r , MonadLogger m , MonadResourceBase m )@@ -384,7 +431,7 @@ selectDistinct = selectDistinctSource >=> runSource --- | Runs a 'C.Source' of rows.+-- | (Internal) Run a 'C.Source' of rows. runSource :: MonadResourceBase m => C.Source (C.ResourceT (SqlPersist m)) r -> SqlPersist m [r]@@ -420,6 +467,16 @@ -- from $ \\appointment -> -- where_ (appointment ^. AppointmentDate <. val now) -- @+--+-- 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:+--+-- @+-- delete $+-- from $ \\(appointment :: SqlExpr (Entity Appointment)) ->+-- return ()+-- @ delete :: ( MonadLogger m , MonadResourceBase m ) => SqlQuery ()@@ -451,7 +508,12 @@ ---------------------------------------------------------------------- --- | Pretty prints a 'SqlQuery' into a SQL query.+-- | (Internal) Pretty prints a 'SqlQuery' into a SQL query.+--+-- Note: if you're curious about the SQL query being generated by+-- @esqueleto@, instead of manually using this function (which is+-- possible but tedious), you may just turn on query logging of+-- @persistent@. toRawSql :: SqlSelect a r => Mode -> Escape -> SqlQuery a -> (TLB.Builder, [PersistValue]) toRawSql mode esc query = let (ret, SideData fromClauses setClauses whereClauses orderByClauses) =@@ -466,6 +528,7 @@ , makeOrderBy esc orderByClauses ] +-- | (Internal) Mode of query being converted by 'toRawSql'. data Mode = SELECT | SELECT_DISTINCT | DELETE | UPDATE @@ -552,8 +615,11 @@ parens b = "(" <> (b <> ")") --- | Class for mapping results coming from 'SqlQuery' into actual--- results.+----------------------------------------------------------------------+++-- | (Internal) Class for mapping results coming from 'SqlQuery'+-- into actual results. -- -- This looks very similar to @RawSql@, and it is! However, -- there are some crucial differences and ultimately they're@@ -571,11 +637,15 @@ -- | Transform a row of the result into the data type. sqlSelectProcessRow :: [PersistValue] -> Either T.Text r ++-- | Not useful for 'select', but used for 'update' and 'delete'. instance SqlSelect () () where sqlSelectCols _ _ = mempty sqlSelectColCount _ = 0 sqlSelectProcessRow _ = Right () ++-- | You may return an 'Entity' from a 'select' query. instance PersistEntity a => SqlSelect (SqlExpr (Entity a)) (Entity a) where sqlSelectCols escape expr@(EEntity ident) = ret where@@ -602,6 +672,8 @@ getEntityVal :: SqlExpr (Entity a) -> a getEntityVal = error "Esqueleto/Sql/getEntityVal" ++-- | You may return a possibly-@NULL@ 'Entity' from a 'select' query. instance PersistEntity a => SqlSelect (SqlExpr (Maybe (Entity a))) (Maybe (Entity a)) where sqlSelectCols escape (EMaybe ent) = sqlSelectCols escape ent sqlSelectColCount = sqlSelectColCount . fromEMaybe@@ -612,6 +684,9 @@ | all (== PersistNull) cols = return Nothing | otherwise = Just <$> sqlSelectProcessRow cols ++-- | You may return any single value (i.e. a single column) from+-- a 'select' query. instance PersistField a => SqlSelect (SqlExpr (Value a)) (Value a) where sqlSelectCols esc (ERaw p f) = let (b, vals) = f esc in (parensM p b, vals)@@ -619,6 +694,9 @@ sqlSelectProcessRow [pv] = Value <$> fromPersistValue pv sqlSelectProcessRow _ = Left "SqlSelect (Value a): wrong number of columns." ++-- | You may return tuples (up to 8-tuples) and tuples of tuples+-- from a 'select' query. instance ( SqlSelect a ra , SqlSelect b rb ) => SqlSelect (a, b) (ra, rb) where@@ -642,6 +720,7 @@ in colCountFst `seq` processRow -- Avoids recalculating 'colCountFst'.+ instance ( SqlSelect a ra , SqlSelect b rb