{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} module Database.Esqueleto.Experimental.From where import qualified Control.Monad.Trans.Writer as W import Data.Coerce (coerce) import Data.Proxy import qualified Data.Text.Lazy.Builder as TLB import Database.Esqueleto.Experimental.ToAlias import Database.Esqueleto.Experimental.ToAliasReference import Database.Esqueleto.Internal.Internal hiding (From(..), from, on) import Database.Esqueleto.Internal.PersistentImport import Database.Persist.Names (EntityNameDB(..)) -- | 'FROM' clause, used to bring entities into scope. -- -- 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' from f = do (a, clause) <- unFrom (toFrom f) Q $ W.tell mempty{sdFromClause=[FromRaw $ clause]} 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 {-# 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) ident <- newIdentFor (coerce $ getEntityDBName ed) let entity = unsafeSqlEntity ident pure $ ( entity, const $ base ident ed ) where base ident@(I identText) def info = let db = coerce $ getEntityDBName def in ( (fromDBName info (coerce db)) <> if db == identText then mempty else " AS " <> useIdent info ident , mempty ) {-# DEPRECATED SubQuery "/Since: 3.4.0.0/ - It is no longer necessary to tag 'SqlQuery' values with @SubQuery@" #-} newtype SubQuery a = SubQuery a instance (SqlSelect a r, ToAlias a, ToAliasReference a) => ToFrom (SubQuery (SqlQuery a)) a where toFrom (SubQuery q) = selectQuery q 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 (ret, sideData) <- Q $ W.censor (\_ -> mempty) $ W.listen $ unQ subquery aliasedValue <- toAlias ret -- Make a fake query with the aliased results, this allows us to ensure that the query is only run once let aliasedQuery = Q $ W.WriterT $ pure (aliasedValue, sideData) -- Add the FromQuery that renders the subquery to our side data subqueryAlias <- newIdentFor (DBName "q") -- Pass the aliased results of the subquery to the outer query -- create aliased references from the outer query results (e.g value from subquery will be `subquery`.`value`), -- this is probably overkill as the aliases should already be unique but seems to be good practice. ref <- toAliasReference subqueryAlias aliasedValue pure (ref, \_ info -> let (queryText,queryVals) = toRawSql SELECT info aliasedQuery in ( (parens queryText) <> " AS " <> useIdent info subqueryAlias , queryVals ) )