    {-# LANGUAGE ConstraintKinds
               , FlexibleContexts
               , FlexibleInstances
               , FunctionalDependencies
               , GADTs
               , MultiParamTypeClasses
               , 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
      ( -- * The pretty face
        SqlQuery
      , SqlExpr
      , SqlEntity
      , select
      , selectSource
      , selectDistinct
      , selectDistinctSource
      , delete
      , deleteCount
      , update
      , updateCount
      , insertSelectDistinct
      , insertSelect
        -- * The guts
      , unsafeSqlBinOp
      , unsafeSqlValue
      , unsafeSqlFunction
      , UnsafeSqlFunctionArgument
      , rawSelectSource
      , runSource
      , rawEsqueleto
      , toRawSql
      , Mode(..)
      , IdentState
      , initialIdentState
      , IdentInfo
      , SqlSelect
      , veryUnsafeCoerceSqlExprValue
      ) where

    import Control.Applicative (Applicative(..), (<$>), (<$))
    import Control.Arrow ((***), first)
    import Control.Exception (throw, throwIO)
    import Control.Monad ((>=>), ap, void, MonadPlus(..))
    import Control.Monad.IO.Class (MonadIO(..))
    import Control.Monad.Logger (MonadLogger)
    import Control.Monad.Trans.Class (lift)
    import Control.Monad.Trans.Resource (MonadResourceBase)
    import Data.Int (Int64)
    import Data.List (intersperse)
    import Data.Monoid (Monoid(..), (<>))
    import Data.Proxy (Proxy(..))
    import Database.Esqueleto.Internal.PersistentImport
    import qualified Control.Monad.Trans.Reader as R
    import qualified Control.Monad.Trans.State as S
    import qualified Control.Monad.Trans.Writer as W
    import qualified Data.Conduit as C
    import qualified Data.Conduit.List as CL
    import qualified Data.HashSet as HS
    import qualified Data.Text as T
    import qualified Data.Text.Lazy as TL
    import qualified Data.Text.Lazy.Builder as TLB
    import qualified Data.Text.Lazy.Builder.Int as TLBI

    import Database.Esqueleto.Internal.Language

    -- | SQL backend for @esqueleto@ using 'SqlPersistT'.
    newtype SqlQuery a =
      Q { unQ :: W.WriterT SideData (S.State IdentState) a }

    instance Functor SqlQuery where
      fmap f = Q . fmap f . unQ

    instance Monad SqlQuery where
      return  = Q . return
      m >>= f = Q (unQ m >>= unQ . f)

    instance Applicative SqlQuery where
      pure  = return
      (<*>) = ap

    -- | Constraint synonym for @persistent@ entities whose backend
    -- is 'SqlPersistT'.
    type SqlEntity ent = (PersistEntity ent, PersistEntityBackend ent ~ SqlBackend)

    ----------------------------------------------------------------------

    -- | Side data written by 'SqlQuery'.
    data SideData = SideData { sdFromClause    :: ![FromClause]
                             , sdSetClause     :: ![SetClause]
                             , sdWhereClause   :: !WhereClause
                             , sdGroupByClause :: !GroupByClause
                             , sdHavingClause  :: !HavingClause
                             , sdOrderByClause :: ![OrderByClause]
                             , sdLimitClause   :: !LimitClause
                             }

    instance Monoid SideData where
      mempty = SideData mempty mempty mempty mempty mempty mempty mempty
      SideData f s w g h o l `mappend` SideData f' s' w' g' h' o' l' =
        SideData (f <> f') (s <> s') (w <> w') (g <> g') (h <> h') (o <> o') (l <> l')

    -- | A part of a @FROM@ clause.
    data FromClause =
        FromStart Ident (EntityDef SqlType)
      | FromJoin FromClause JoinKind FromClause (Maybe (SqlExpr (Value Bool)))
      | OnClause (SqlExpr (Value Bool))

    -- | A part of a @SET@ clause.
    newtype SetClause = SetClause (SqlExpr (Value ()))

    -- | Collect 'OnClause's on 'FromJoin's.  Returns the first
    -- unmatched 'OnClause's data on error.  Returns a list without
    -- 'OnClauses' on success.
    collectOnClauses :: [FromClause] -> Either (SqlExpr (Value Bool)) [FromClause]
    collectOnClauses = go []
      where
        go []  (f@(FromStart _ _):fs) = fmap (f:) (go [] fs) -- fast path
        go acc (OnClause expr    :fs) = findMatching acc expr >>= flip go fs
        go acc (f:fs)                 = go (f:acc) fs
        go acc []                     = return $ reverse acc

        findMatching (f : acc) expr =
          case tryMatch expr f of
            Just f' -> return (f' : acc)
            Nothing -> (f:) <$> findMatching acc expr
        findMatching [] expr = Left expr

        tryMatch expr (FromJoin l k r onClause) =
          matchR `mplus` matchC `mplus` matchL -- right to left
            where
              matchR = (\r' -> FromJoin l k r' onClause) <$> tryMatch expr r
              matchL = (\l' -> FromJoin l' k r onClause) <$> tryMatch expr l
              matchC = case onClause of
                         Nothing -> return (FromJoin l k r (Just expr))
                         Just _  -> mzero
        tryMatch _ _ = mzero

    -- | A complete @WHERE@ clause.
    data WhereClause = Where (SqlExpr (Value Bool))
                     | NoWhere

    instance Monoid WhereClause where
      mempty = NoWhere
      NoWhere  `mappend` w        = w
      w        `mappend` NoWhere  = w
      Where e1 `mappend` Where e2 = Where (e1 &&. e2)

    -- | A @GROUP BY@ clause.
    newtype GroupByClause = GroupBy [SomeValue SqlExpr]

    instance Monoid GroupByClause where
      mempty = GroupBy []
      GroupBy fs `mappend` GroupBy fs' = GroupBy (fs <> fs')

    -- | A @HAVING@ cause.
    type HavingClause = WhereClause

    -- | A @ORDER BY@ clause.
    type OrderByClause = SqlExpr OrderBy

    -- | A @LIMIT@ clause.
    data LimitClause = Limit (Maybe Int64) (Maybe Int64)

    instance Monoid LimitClause where
      mempty = Limit mzero mzero
      Limit l1 o1 `mappend` Limit l2 o2 =
        Limit (l2 `mplus` l1) (o2 `mplus` o1)
        -- More than one 'limit' or 'offset' is issued, we want to
        -- keep the latest one.  That's why we use mplus with
        -- "reversed" arguments.

    ----------------------------------------------------------------------

    -- | Identifier used for table names.
    newtype Ident = I T.Text

    -- | List of identifiers already in use and supply of temporary
    -- identifiers.
    newtype IdentState = IdentState { inUse :: HS.HashSet T.Text }

    initialIdentState :: IdentState
    initialIdentState = IdentState mempty

    -- | Create a fresh 'Ident'.  If possible, use the given
    -- 'DBName'.
    newIdentFor :: DBName -> SqlQuery Ident
    newIdentFor = Q . lift . try . unDBName
      where
        try orig = do
          s <- S.get
          let go (t:ts) | t `HS.member` inUse s = go ts
                        | otherwise             = use t
              go [] = error "Esqueleto/Sql/newIdentFor: never here"
          go (possibilities orig)

        possibilities t = t : map addNum [2..]
          where
            addNum :: Int -> T.Text
            addNum = T.append t . T.pack . show

        use t = do
          S.modify (\s -> s { inUse = HS.insert t (inUse s) })
          return (I t)

    -- | Information needed to escape and use identifiers.
    type IdentInfo = (Connection, IdentState)

    -- | Use an identifier.
    useIdent :: IdentInfo -> Ident -> TLB.Builder
    useIdent info (I ident) = fromDBName info $ DBName ident

    ----------------------------------------------------------------------

    -- | An expression on the SQL backend.
    data SqlExpr a where
      -- An entity, created by 'from' (cf. 'fromStart').
      EEntity  :: Ident -> SqlExpr (Entity val)

      -- Just a tag stating that something is nullable.
      EMaybe   :: SqlExpr a -> SqlExpr (Maybe a)

      -- 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.
      ERaw     :: NeedParens -> (IdentInfo -> (TLB.Builder, [PersistValue])) -> SqlExpr (Value a)

      -- 'EList' and 'EEmptyList' are used by list operators.
      EList      :: SqlExpr (Value a) -> SqlExpr (ValueList a)
      EEmptyList :: SqlExpr (ValueList a)

      -- A 'SqlExpr' accepted only by 'orderBy'.
      EOrderBy :: OrderByType -> SqlExpr (Value a) -> SqlExpr OrderBy

      -- A 'SqlExpr' accepted only by 'set'.
      ESet :: (SqlExpr (Entity val) -> SqlExpr (Value ())) -> SqlExpr (Update val)

      -- An internal 'SqlExpr' used by the 'from' hack.
      EPreprocessedFrom :: a -> FromClause -> SqlExpr (PreprocessedFrom a)

      -- Used by 'insertSelect'.
      EInsert  :: Proxy a -> (IdentInfo -> (TLB.Builder, [PersistValue])) -> SqlExpr (Insertion a)

    data NeedParens = Parens | Never

    parensM :: NeedParens -> TLB.Builder -> TLB.Builder
    parensM Never  = id
    parensM Parens = parens

    data OrderByType = ASC | DESC

    instance Esqueleto SqlQuery SqlExpr SqlBackend where
      fromStart = x
        where
          x = do
            let ed = entityDef (getVal x)
            ident <- newIdentFor (entityDB ed)
            let ret   = EEntity ident
                from_ = FromStart ident ed
            return (EPreprocessedFrom ret from_)
          getVal :: SqlQuery (SqlExpr (PreprocessedFrom (SqlExpr (Entity a)))) -> Proxy a
          getVal = const Proxy

      fromStartMaybe = maybelize <$> fromStart
        where
          maybelize :: SqlExpr (PreprocessedFrom (SqlExpr (Entity a)))
                    -> SqlExpr (PreprocessedFrom (SqlExpr (Maybe (Entity a))))
          maybelize (EPreprocessedFrom ret from_) = EPreprocessedFrom (EMaybe ret) from_

      fromJoin (EPreprocessedFrom lhsRet lhsFrom)
               (EPreprocessedFrom rhsRet rhsFrom) = Q $ do
        let ret   = smartJoin lhsRet rhsRet
            from_ = FromJoin lhsFrom             -- LHS
                             (reifyJoinKind ret) -- JOIN
                             rhsFrom             -- RHS
                             Nothing             -- ON
        return (EPreprocessedFrom ret from_)

      fromFinish (EPreprocessedFrom ret from_) = Q $ do
        W.tell mempty { sdFromClause = [from_] }
        return ret

      where_ expr = Q $ W.tell mempty { sdWhereClause = Where expr }

      on expr = Q $ W.tell mempty { sdFromClause = [OnClause expr] }

      groupBy expr = Q $ W.tell mempty { sdGroupByClause = GroupBy $ toSomeValues expr }

      having expr = Q $ W.tell mempty { sdHavingClause = Where expr }

      orderBy exprs = Q $ W.tell mempty { sdOrderByClause = exprs }
      asc  = EOrderBy ASC
      desc = EOrderBy DESC

      limit  n = Q $ W.tell mempty { sdLimitClause = Limit (Just n) Nothing  }
      offset n = Q $ W.tell mempty { sdLimitClause = Limit Nothing  (Just n) }

      sub_select         = sub SELECT
      sub_selectDistinct = sub SELECT_DISTINCT

      EEntity ident ^. field =
        ERaw Never $ \info -> (useIdent info ident <> ("." <> fieldName info field), [])

      EMaybe r ?. field = maybelize (r ^. field)
        where
          maybelize :: SqlExpr (Value a) -> SqlExpr (Value (Maybe a))
          maybelize (ERaw p f) = ERaw p f

      val = ERaw Never . const . (,) "?" . return . toPersistValue

      isNothing (ERaw p f) = ERaw Parens $ first ((<> " IS NULL") . parensM p) . f
      just (ERaw p f) = ERaw p f
      nothing   = unsafeSqlValue "NULL"
      joinV (ERaw p f) = ERaw p f
      countRows = unsafeSqlValue "COUNT(*)"
      count (ERaw _ f) = ERaw Never $ \info -> let (b, vals) = f info
                                               in ("COUNT" <> parens b, vals)

      not_ (ERaw p f) = ERaw Never $ \info -> let (b, vals) = f info
                                              in ("NOT " <> parensM p b, vals)

      (==.) = unsafeSqlBinOp " = "
      (>=.) = unsafeSqlBinOp " >= "
      (>.)  = unsafeSqlBinOp " > "
      (<=.) = unsafeSqlBinOp " <= "
      (<.)  = unsafeSqlBinOp " < "
      (!=.) = unsafeSqlBinOp " != "
      (&&.) = unsafeSqlBinOp " AND "
      (||.) = unsafeSqlBinOp " OR "
      (+.)  = unsafeSqlBinOp " + "
      (-.)  = unsafeSqlBinOp " - "
      (/.)  = unsafeSqlBinOp " / "
      (*.)  = unsafeSqlBinOp " * "

      random_  = unsafeSqlValue "RANDOM()"
      sum_     = unsafeSqlFunction "SUM"
      round_   = unsafeSqlFunction "ROUND"
      ceiling_ = unsafeSqlFunction "CEILING"
      floor_   = unsafeSqlFunction "FLOOR"
      avg_     = unsafeSqlFunction "AVG"
      min_     = unsafeSqlFunction "MIN"
      max_     = unsafeSqlFunction "MAX"

      like    = unsafeSqlBinOp    " LIKE "
      (%)     = unsafeSqlValue    "'%'"
      concat_ = unsafeSqlFunction "CONCAT"
      (++.)   = unsafeSqlBinOp    " || "

      subList_select         = EList . sub_select
      subList_selectDistinct = EList . sub_selectDistinct

      valList []   = EEmptyList
      valList vals = EList $ ERaw Parens $ const ( uncommas ("?" <$ vals)
                                                 , map toPersistValue vals )

      v `in_`   e = ifNotEmptyList e False $ unsafeSqlBinOp     " IN " v (veryUnsafeCoerceSqlExprValueList e)
      v `notIn` e = ifNotEmptyList e True  $ unsafeSqlBinOp " NOT IN " v (veryUnsafeCoerceSqlExprValueList e)

      exists    = unsafeSqlFunction     "EXISTS " . existsHelper
      notExists = unsafeSqlFunction "NOT EXISTS " . existsHelper

      set ent upds = Q $ W.tell mempty { sdSetClause = map apply upds }
        where
          apply (ESet f) = SetClause (f ent)

      field  =. expr = setAux field (const expr)
      field +=. expr = setAux field (\ent -> ent ^. field +. expr)
      field -=. expr = setAux field (\ent -> ent ^. field -. expr)
      field *=. expr = setAux field (\ent -> ent ^. field *. expr)
      field /=. expr = setAux field (\ent -> ent ^. field /. expr)

      (<#) _ (ERaw _ f) = EInsert Proxy f

      (EInsert _ f) <&> (ERaw _ g) = EInsert Proxy $ \x ->
        let (fb, fv) = f x
            (gb, gv) = g x
        in (fb <> ", " <> gb, fv ++ gv)

    instance ToSomeValues SqlExpr (SqlExpr (Value a)) where
      toSomeValues a = [SomeValue a]

    fieldName :: (PersistEntity val, PersistField typ)
              => IdentInfo -> EntityField val typ -> TLB.Builder
    fieldName info = fromDBName info . fieldDB . persistFieldDef

    setAux :: (PersistEntity val, PersistField typ)
           => EntityField val typ
           -> (SqlExpr (Entity val) -> SqlExpr (Value typ))
           -> SqlExpr (Update val)
    setAux field mkVal = ESet $ \ent -> unsafeSqlBinOp " = " name (mkVal ent)
      where name = ERaw Never $ \info -> (fieldName info field, mempty)

    sub :: PersistField a => Mode -> SqlQuery (SqlExpr (Value a)) -> SqlExpr (Value a)
    sub mode query = ERaw Parens $ \info -> toRawSql mode pureQuery info query

    fromDBName :: IdentInfo -> DBName -> TLB.Builder
    fromDBName (conn, _) = TLB.fromText . connEscapeName conn

    existsHelper :: SqlQuery () -> SqlExpr (Value Bool)
    existsHelper = sub SELECT . (>> return true)
      where
        true :: SqlExpr (Value Bool)
        true = val True

    ifNotEmptyList :: SqlExpr (ValueList a) -> Bool -> SqlExpr (Value Bool) -> SqlExpr (Value Bool)
    ifNotEmptyList EEmptyList b _ = val b
    ifNotEmptyList (EList _)  _ x = x

    ----------------------------------------------------------------------

    -- | (Internal) Create a custom binary operator.  You /should/
    -- /not/ use this function directly since its type is very
    -- general, you should always use it with an explicit type
    -- signature.  For example:
    --
    -- @
    -- (==.) :: SqlExpr (Value a) -> SqlExpr (Value a) -> SqlExpr (Value Bool)
    -- (==.) = unsafeSqlBinOp " = "
    -- @
    --
    -- In the example above, we constraint the arguments to be of the
    -- same type and constraint the result to be a boolean value.
    unsafeSqlBinOp :: TLB.Builder -> SqlExpr (Value a) -> SqlExpr (Value b) -> SqlExpr (Value c)
    unsafeSqlBinOp op (ERaw p1 f1) (ERaw p2 f2) = ERaw Parens f
      where
        f info = let (b1, vals1) = f1 info
                     (b2, vals2) = f2 info
                 in ( parensM p1 b1 <> op <> parensM p2 b2
                    , vals1 <> vals2 )
    {-# INLINE unsafeSqlBinOp #-}

    -- | (Internal) A raw SQL value.  The same warning from
    -- 'unsafeSqlBinOp' applies to this function as well.
    unsafeSqlValue :: TLB.Builder -> SqlExpr (Value a)
    unsafeSqlValue v = ERaw Never $ \_ -> (v, mempty)
    {-# INLINE unsafeSqlValue #-}

    -- | (Internal) A raw SQL function.  Once again, the same warning
    -- from 'unsafeSqlBinOp' applies to this function as well.
    unsafeSqlFunction :: UnsafeSqlFunctionArgument a =>
                         TLB.Builder -> a -> SqlExpr (Value b)
    unsafeSqlFunction name arg =
      ERaw Never $ \info ->
        let (argsTLB, argsVals) =
              uncommas' $ map (\(ERaw _ f) -> f info) $ toArgList arg
        in (name <> parens argsTLB, argsVals)

    class UnsafeSqlFunctionArgument a where
      toArgList :: a -> [SqlExpr (Value ())]
    instance (a ~ Value b) => UnsafeSqlFunctionArgument (SqlExpr a) where
      toArgList = (:[]) . veryUnsafeCoerceSqlExprValue
    instance UnsafeSqlFunctionArgument a =>
             UnsafeSqlFunctionArgument [a] where
      toArgList = concatMap toArgList
    instance ( UnsafeSqlFunctionArgument a
             , UnsafeSqlFunctionArgument b
             ) => UnsafeSqlFunctionArgument (a, b) where
      toArgList (a, b) = toArgList a ++ toArgList b
    instance ( UnsafeSqlFunctionArgument a
             , UnsafeSqlFunctionArgument b
             , UnsafeSqlFunctionArgument c
             ) => UnsafeSqlFunctionArgument (a, b, c) where
      toArgList = toArgList . from3
    instance ( UnsafeSqlFunctionArgument a
             , UnsafeSqlFunctionArgument b
             , UnsafeSqlFunctionArgument c
             , UnsafeSqlFunctionArgument d
             ) => UnsafeSqlFunctionArgument (a, b, c, d) where
      toArgList = toArgList . from4

    -- | (Internal) Coerce a value's type from 'SqlExpr (Value a)' to
    -- 'SqlExpr (Value b)'.  You should /not/ use this function
    -- unless you know what you're doing!
    veryUnsafeCoerceSqlExprValue :: SqlExpr (Value a) -> SqlExpr (Value b)
    veryUnsafeCoerceSqlExprValue (ERaw p f) = ERaw p f

    -- | (Internal) Coerce a value's type from 'SqlExpr (ValueList
    -- a)' to 'SqlExpr (Value a)'.  Does not work with empty lists.
    veryUnsafeCoerceSqlExprValueList :: SqlExpr (ValueList a) -> SqlExpr (Value a)
    veryUnsafeCoerceSqlExprValueList (EList v)  = v
    veryUnsafeCoerceSqlExprValueList EEmptyList =
      error "veryUnsafeCoerceSqlExprValueList: empty list."

    ----------------------------------------------------------------------

    -- | (Internal) Execute an @esqueleto@ @SELECT@ 'SqlQuery' inside
    -- @persistent@'s 'SqlPersistT' monad.
    rawSelectSource :: ( SqlSelect a r
                       , MonadLogger m
                       , MonadResourceBase m )
                     => Mode
                     -> SqlQuery a
                     -> SqlPersistT m (C.Source (C.ResourceT (SqlPersistT m)) r)
    rawSelectSource mode query = src
        where
          src = do
            conn <- SqlPersistT R.ask
            return $ run conn C.$= massage

          run conn =
            uncurry rawQuery $
            first builderToText $
            toRawSql mode pureQuery (conn, initialIdentState) query

          massage = do
            mrow <- C.await
            case process <$> mrow of
              Just (Right r)  -> C.yield r >> massage
              Just (Left err) -> liftIO $ throwIO $ PersistMarshalError err
              Nothing         -> return ()

          process = sqlSelectProcessRow

    -- | Execute an @esqueleto@ @SELECT@ query inside @persistent@'s
    -- 'SqlPersistT' monad and return a 'C.Source' of rows.
    selectSource :: ( SqlSelect a r
                    , MonadLogger m
                    , MonadResourceBase m )
                 => SqlQuery a
                 -> SqlPersistT m (C.Source (C.ResourceT (SqlPersistT m)) r)
    selectSource = rawSelectSource SELECT

    -- | Execute an @esqueleto@ @SELECT@ query inside @persistent@'s
    -- 'SqlPersistT' 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 )
           => SqlQuery a -> SqlPersistT m [r]
    select = selectSource >=> runSource

    -- | Execute an @esqueleto@ @SELECT DISTINCT@ query inside
    -- @persistent@'s 'SqlPersistT' monad and return a 'C.Source' of
    -- rows.
    selectDistinctSource
      :: ( SqlSelect a r
         , MonadLogger m
         , MonadResourceBase m )
      => SqlQuery a
      -> SqlPersistT m (C.Source (C.ResourceT (SqlPersistT m)) r)
    selectDistinctSource = rawSelectSource SELECT_DISTINCT

    -- | Execute an @esqueleto@ @SELECT DISTINCT@ query inside
    -- @persistent@'s 'SqlPersistT' monad and return a list of rows.
    selectDistinct :: ( SqlSelect a r
                      , MonadLogger m
                      , MonadResourceBase m )
                   => SqlQuery a -> SqlPersistT m [r]
    selectDistinct = selectDistinctSource >=> runSource

    -- | (Internal) Run a 'C.Source' of rows.
    runSource :: MonadResourceBase m =>
                 C.Source (C.ResourceT (SqlPersistT m)) r
              -> SqlPersistT m [r]
    runSource src = C.runResourceT $ src C.$$ CL.consume

    ----------------------------------------------------------------------

    -- | (Internal) Execute an @esqueleto@ statement inside
    -- @persistent@'s 'SqlPersistT' monad.
    rawEsqueleto :: ( MonadLogger m
                  , MonadResourceBase m )
               => Mode
               -> SqlQuery ()
               -> SqlPersistT m Int64
    rawEsqueleto mode query = do
      conn <- SqlPersistT R.ask
      uncurry rawExecuteCount $
        first builderToText $
        toRawSql mode pureQuery (conn, initialIdentState) query

    -- | Execute an @esqueleto@ @DELETE@ query inside @persistent@'s
    -- 'SqlPersistT' monad.  Note that currently there are no type
    -- checks for statements that should not appear on a @DELETE@
    -- query.
    --
    -- Example of usage:
    --
    -- @
    -- delete $
    -- 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 ()
           -> SqlPersistT m ()
    delete = void . deleteCount

    -- | Same as 'delete', but returns the number of rows affected.
    deleteCount :: ( MonadLogger m
                   , MonadResourceBase m )
                => SqlQuery ()
                -> SqlPersistT m Int64
    deleteCount = rawEsqueleto DELETE

    -- | Execute an @esqueleto@ @UPDATE@ query inside @persistent@'s
    -- 'SqlPersistT' monad.  Note that currently there are no type
    -- checks for statements that should not appear on a @UPDATE@
    -- query.
    --
    -- Example of usage:
    --
    -- @
    -- update $ \p -> do
    -- set p [ PersonAge =. just (val thisYear) -. p ^. PersonBorn ]
    -- where_ $ isNull (p ^. PersonAge)
    -- @
    update :: ( MonadLogger m
              , MonadResourceBase m
              , SqlEntity val )
           => (SqlExpr (Entity val) -> SqlQuery ())
           -> SqlPersistT m ()
    update = void . updateCount

    -- | Same as 'update', but returns the number of rows affected.
    updateCount :: ( MonadLogger m
                   , MonadResourceBase m
                   , SqlEntity val )
                => (SqlExpr (Entity val) -> SqlQuery ())
                -> SqlPersistT m Int64
    updateCount = rawEsqueleto UPDATE . from

    ----------------------------------------------------------------------

    builderToText :: TLB.Builder -> T.Text
    builderToText = TL.toStrict . TLB.toLazyTextWith defaultChunkSize
      where
        defaultChunkSize = 1024 - 32

    -- | (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 -> QueryType a -> IdentInfo -> SqlQuery a -> (TLB.Builder, [PersistValue])
    toRawSql mode qt (conn, firstIdentState) query =
      let ((ret, sd), finalIdentState) =
            flip S.runState firstIdentState $
            W.runWriterT $
            unQ query
          SideData fromClauses
                   setClauses
                   whereClauses
                   groupByClause
                   havingClause
                   orderByClauses
                   limitClause = sd
          -- Pass the finalIdentState (containing all identifiers
          -- that were used) to the subsequent calls.  This ensures
          -- that no name clashes will occur on subqueries that may
          -- appear on the expressions below.
          info = (conn, finalIdentState)
      in mconcat
          [ makeInsert  qt ret
          , makeSelect  info mode ret
          , makeFrom    info mode fromClauses
          , makeSet     info setClauses
          , makeWhere   info whereClauses
          , makeGroupBy info groupByClause
          , makeHaving  info havingClause
          , makeOrderBy info orderByClauses
          , makeLimit   info limitClause
          ]

    -- | (Internal) Mode of query being converted by 'toRawSql'.
    data Mode = SELECT | SELECT_DISTINCT | DELETE | UPDATE

    newtype QueryType a = QueryType { unQueryType :: a -> TLB.Builder }

    pureQuery :: QueryType a
    pureQuery = QueryType (const mempty)

    insertQuery :: PersistEntity a => QueryType (SqlExpr (Insertion a))
    insertQuery = QueryType $ \(EInsert p _)->
        let def = entityDef p
            unName = TLB.fromText . unDBName
            fields = uncommas $ map (unName . fieldDB) (entityFields def)
            table = unName . entityDB . entityDef $ p
        in "INSERT INTO " <> table <> parens fields <> "\n"

    makeInsert :: QueryType a -> a -> (TLB.Builder, [PersistValue])
    makeInsert q a = (unQueryType q a, [])

    uncommas :: [TLB.Builder] -> TLB.Builder
    uncommas = mconcat . intersperse ", " . filter (/= mempty)

    uncommas' :: Monoid a => [(TLB.Builder, a)] -> (TLB.Builder, a)
    uncommas' = (uncommas *** mconcat) . unzip

    makeSelect :: SqlSelect a r => IdentInfo -> Mode -> a -> (TLB.Builder, [PersistValue])
    makeSelect info mode ret =
      case mode of
        SELECT          -> withCols "SELECT "
        SELECT_DISTINCT -> withCols "SELECT DISTINCT "
        DELETE          -> plain "DELETE "
        UPDATE          -> plain "UPDATE "
      where
        withCols v = first (v <>) (sqlSelectCols info ret)
        plain    v = (v, [])

    makeFrom :: IdentInfo -> Mode -> [FromClause] -> (TLB.Builder, [PersistValue])
    makeFrom _    _    [] = mempty
    makeFrom info mode fs = ret
      where
        ret = case collectOnClauses fs of
                Left expr -> throw $ mkExc expr
                Right fs' -> keyword $ uncommas' (map (mk Never mempty) fs')
        keyword = case mode of
                    UPDATE -> id
                    _      -> first ("\nFROM " <>)

        mk _     onClause (FromStart i def) = base i def <> onClause
        mk paren onClause (FromJoin lhs kind rhs monClause) =
          first (parensM paren) $
          mconcat [ mk Parens onClause lhs
                  , (fromKind kind, mempty)
                  , mk Never (maybe mempty makeOnClause monClause) rhs
                  ]
        mk _ _ (OnClause _) = error "Esqueleto/Sql/makeFrom: never here (is collectOnClauses working?)"

        base ident@(I identText) def =
          let db@(DBName dbText) = entityDB def
          in ( if dbText == identText
               then fromDBName info db
               else fromDBName info db <> (" AS " <> useIdent info ident)
             , mempty )

        fromKind InnerJoinKind      = " INNER JOIN "
        fromKind CrossJoinKind      = " CROSS JOIN "
        fromKind LeftOuterJoinKind  = " LEFT OUTER JOIN "
        fromKind RightOuterJoinKind = " RIGHT OUTER JOIN "
        fromKind FullOuterJoinKind  = " FULL OUTER JOIN "

        makeOnClause (ERaw _ f) = first (" ON " <>) (f info)

        mkExc :: SqlExpr (Value Bool) -> OnClauseWithoutMatchingJoinException
        mkExc (ERaw _ f) =
          OnClauseWithoutMatchingJoinException $
          TL.unpack $ TLB.toLazyText $ fst (f info)

    makeSet :: IdentInfo -> [SetClause] -> (TLB.Builder, [PersistValue])
    makeSet _    [] = mempty
    makeSet info os = first ("\nSET " <>) $ uncommas' (map mk os)
      where
        mk (SetClause (ERaw _ f)) = f info

    makeWhere :: IdentInfo -> WhereClause -> (TLB.Builder, [PersistValue])
    makeWhere _    NoWhere            = mempty
    makeWhere info (Where (ERaw _ f)) = first ("\nWHERE " <>) (f info)

    makeGroupBy :: IdentInfo -> GroupByClause -> (TLB.Builder, [PersistValue])
    makeGroupBy _ (GroupBy []) = (mempty, [])
    makeGroupBy info (GroupBy fields) = first ("\nGROUP BY " <>) build
      where
        build = uncommas' $ map (\(SomeValue (ERaw _ f)) -> f info) fields

    makeHaving :: IdentInfo -> WhereClause -> (TLB.Builder, [PersistValue])
    makeHaving _    NoWhere            = mempty
    makeHaving info (Where (ERaw _ f)) = first ("\nHAVING " <>) (f info)

    makeOrderBy :: IdentInfo -> [OrderByClause] -> (TLB.Builder, [PersistValue])
    makeOrderBy _    [] = mempty
    makeOrderBy info os = first ("\nORDER BY " <>) $ uncommas' (map mk os)
      where
        mk (EOrderBy t (ERaw p f)) = first ((<> orderByType t) . parensM p) (f info)
        orderByType ASC  = " ASC"
        orderByType DESC = " DESC"

    makeLimit :: IdentInfo -> LimitClause -> (TLB.Builder, [PersistValue])
    makeLimit _    (Limit Nothing Nothing)  = mempty
    makeLimit _    (Limit Nothing (Just 0)) = mempty
    makeLimit info (Limit ml      mo)       = (ret, mempty)
      where
        ret = TLB.singleton '\n' <> (limitTLB <> offsetTLB)

        limitTLB =
          case ml of
            Just l  -> "LIMIT " <> TLBI.decimal l
            Nothing -> TLB.fromText (connNoLimit $ fst info)

        offsetTLB =
          case mo of
            Just o  -> " OFFSET " <> TLBI.decimal o
            Nothing -> mempty

    parens :: TLB.Builder -> TLB.Builder
    parens b = "(" <> (b <> ")")

    ----------------------------------------------------------------------

    -- | (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
    -- different classes.
    class SqlSelect a r | a -> r, r -> a where
      -- | Creates the variable part of the @SELECT@ query and
      -- returns the list of 'PersistValue's that will be given to
      -- 'rawQuery'.
      sqlSelectCols :: IdentInfo -> a -> (TLB.Builder, [PersistValue])

      -- | Number of columns that will be consumed.
      sqlSelectColCount :: Proxy a -> Int

      -- | Transform a row of the result into the data type.
      sqlSelectProcessRow :: [PersistValue] -> Either T.Text r

    -- | You may return an insertion of some PersistEntity
    instance PersistEntity a => SqlSelect (SqlExpr (Insertion a)) (Insertion a) where
      sqlSelectCols info (EInsert _ f) = f info
      sqlSelectColCount = const 0
      sqlSelectProcessRow = const (Right (error msg))
        where
          msg = "sqlSelectProcessRow/SqlSelect (SqlExpr (Insertion a)) (Insertion a): never here"

    -- | Not useful for 'select', but used for 'update' and 'delete'.
    instance SqlSelect () () where
      sqlSelectCols _ _ = ("1", [])
      sqlSelectColCount _ = 1
      sqlSelectProcessRow _ = Right ()

    -- | You may return an 'Entity' from a 'select' query.
    instance PersistEntity a => SqlSelect (SqlExpr (Entity a)) (Entity a) where
      sqlSelectCols info expr@(EEntity ident) = ret
          where
            process ed = uncommas $
                         map ((name <>) . fromDBName info) $
                         (entityID ed:) $
                         map fieldDB $
                         entityFields ed
            -- 'name' is the biggest difference between 'RawSql' and
            -- 'SqlSelect'.  We automatically create names for tables
            -- (since it's not the user who's writing the FROM
            -- clause), while 'rawSql' assumes that it's just the
            -- name of the table (which doesn't allow self-joins, for
            -- example).
            name = useIdent info ident <> "."
            ret = let ed = entityDef $ getEntityVal $ return expr
                  in (process ed, mempty)
      sqlSelectColCount = (+1) . length . entityFields . entityDef . getEntityVal
      sqlSelectProcessRow (idCol:ent) =
        Entity <$> fromPersistValue idCol
               <*> fromPersistValues ent
      sqlSelectProcessRow _ = Left "SqlSelect (Entity a): wrong number of columns."

    getEntityVal :: Proxy (SqlExpr (Entity a)) -> Proxy a
    getEntityVal = const Proxy

    -- | You may return a possibly-@NULL@ 'Entity' from a 'select' query.
    instance PersistEntity a => SqlSelect (SqlExpr (Maybe (Entity a))) (Maybe (Entity a)) where
      sqlSelectCols info (EMaybe ent) = sqlSelectCols info ent
      sqlSelectColCount = sqlSelectColCount . fromEMaybe
        where
          fromEMaybe :: Proxy (SqlExpr (Maybe e)) -> Proxy (SqlExpr e)
          fromEMaybe = const Proxy
      sqlSelectProcessRow cols
        | 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 info (ERaw p f) = let (b, vals) = f info
                                      in (parensM p b, vals)
      sqlSelectColCount = const 1
      sqlSelectProcessRow [pv] = Value <$> fromPersistValue pv
      sqlSelectProcessRow _    = Left "SqlSelect (Value a): wrong number of columns."

    -- | You may return tuples (up to 16-tuples) and tuples of tuples
    -- from a 'select' query.
    instance ( SqlSelect a ra
             , SqlSelect b rb
             ) => SqlSelect (a, b) (ra, rb) where
      sqlSelectCols esc (a, b) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          ]
      sqlSelectColCount = uncurry (+) . (sqlSelectColCount *** sqlSelectColCount) . fromTuple
        where
          fromTuple :: Proxy (a,b) -> (Proxy a, Proxy b)
          fromTuple = const (Proxy, Proxy)
      sqlSelectProcessRow =
        let x = getType processRow
            getType :: SqlSelect a r => (z -> Either y (r,x)) -> Proxy a
            getType = const Proxy

            colCountFst = sqlSelectColCount x

            processRow row =
                let (rowFst, rowSnd) = splitAt colCountFst row
                in (,) <$> sqlSelectProcessRow rowFst
                       <*> sqlSelectProcessRow rowSnd

        in colCountFst `seq` processRow
           -- Avoids recalculating 'colCountFst'.

    instance ( SqlSelect a ra
             , SqlSelect b rb
             , SqlSelect c rc
             ) => SqlSelect (a, b, c) (ra, rb, rc) where
      sqlSelectCols esc (a, b, c) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          , sqlSelectCols esc c
          ]
      sqlSelectColCount   = sqlSelectColCount . from3P
      sqlSelectProcessRow = fmap to3 . sqlSelectProcessRow

    from3P :: Proxy (a,b,c) -> Proxy ((a,b),c)
    from3P = const Proxy

    from3 :: (a,b,c) -> ((a,b),c)
    from3 (a,b,c) = ((a,b),c)

    to3 :: ((a,b),c) -> (a,b,c)
    to3 ((a,b),c) = (a,b,c)

    instance ( SqlSelect a ra
             , SqlSelect b rb
             , SqlSelect c rc
             , SqlSelect d rd
             ) => SqlSelect (a, b, c, d) (ra, rb, rc, rd) where
      sqlSelectCols esc (a, b, c, d) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          , sqlSelectCols esc c
          , sqlSelectCols esc d
          ]
      sqlSelectColCount   = sqlSelectColCount . from4P
      sqlSelectProcessRow = fmap to4 . sqlSelectProcessRow

    from4P :: Proxy (a,b,c,d) -> Proxy ((a,b),(c,d))
    from4P = const Proxy

    from4 :: (a,b,c,d) -> ((a,b),(c,d))
    from4 (a,b,c,d) = ((a,b),(c,d))

    to4 :: ((a,b),(c,d)) -> (a,b,c,d)
    to4 ((a,b),(c,d)) = (a,b,c,d)

    instance ( SqlSelect a ra
             , SqlSelect b rb
             , SqlSelect c rc
             , SqlSelect d rd
             , SqlSelect e re
             ) => SqlSelect (a, b, c, d, e) (ra, rb, rc, rd, re) where
      sqlSelectCols esc (a, b, c, d, e) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          , sqlSelectCols esc c
          , sqlSelectCols esc d
          , sqlSelectCols esc e
          ]
      sqlSelectColCount   = sqlSelectColCount . from5P
      sqlSelectProcessRow = fmap to5 . sqlSelectProcessRow

    from5P :: Proxy (a,b,c,d,e) -> Proxy ((a,b),(c,d),e)
    from5P = const Proxy

    to5 :: ((a,b),(c,d),e) -> (a,b,c,d,e)
    to5 ((a,b),(c,d),e) = (a,b,c,d,e)

    instance ( SqlSelect a ra
             , SqlSelect b rb
             , SqlSelect c rc
             , SqlSelect d rd
             , SqlSelect e re
             , SqlSelect f rf
             ) => SqlSelect (a, b, c, d, e, f) (ra, rb, rc, rd, re, rf) where
      sqlSelectCols esc (a, b, c, d, e, f) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          , sqlSelectCols esc c
          , sqlSelectCols esc d
          , sqlSelectCols esc e
          , sqlSelectCols esc f
          ]
      sqlSelectColCount   = sqlSelectColCount . from6P
      sqlSelectProcessRow = fmap to6 . sqlSelectProcessRow

    from6P :: Proxy (a,b,c,d,e,f) -> Proxy ((a,b),(c,d),(e,f))
    from6P = const Proxy

    to6 :: ((a,b),(c,d),(e,f)) -> (a,b,c,d,e,f)
    to6 ((a,b),(c,d),(e,f)) = (a,b,c,d,e,f)

    instance ( SqlSelect a ra
             , SqlSelect b rb
             , SqlSelect c rc
             , SqlSelect d rd
             , SqlSelect e re
             , SqlSelect f rf
             , SqlSelect g rg
             ) => SqlSelect (a, b, c, d, e, f, g) (ra, rb, rc, rd, re, rf, rg) where
      sqlSelectCols esc (a, b, c, d, e, f, g) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          , sqlSelectCols esc c
          , sqlSelectCols esc d
          , sqlSelectCols esc e
          , sqlSelectCols esc f
          , sqlSelectCols esc g
          ]
      sqlSelectColCount   = sqlSelectColCount . from7P
      sqlSelectProcessRow = fmap to7 . sqlSelectProcessRow

    from7P :: Proxy (a,b,c,d,e,f,g) -> Proxy ((a,b),(c,d),(e,f),g)
    from7P = const Proxy

    to7 :: ((a,b),(c,d),(e,f),g) -> (a,b,c,d,e,f,g)
    to7 ((a,b),(c,d),(e,f),g) = (a,b,c,d,e,f,g)

    instance ( SqlSelect a ra
             , SqlSelect b rb
             , SqlSelect c rc
             , SqlSelect d rd
             , SqlSelect e re
             , SqlSelect f rf
             , SqlSelect g rg
             , SqlSelect h rh
             ) => SqlSelect (a, b, c, d, e, f, g, h) (ra, rb, rc, rd, re, rf, rg, rh) where
      sqlSelectCols esc (a, b, c, d, e, f, g, h) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          , sqlSelectCols esc c
          , sqlSelectCols esc d
          , sqlSelectCols esc e
          , sqlSelectCols esc f
          , sqlSelectCols esc g
          , sqlSelectCols esc h
          ]
      sqlSelectColCount   = sqlSelectColCount . from8P
      sqlSelectProcessRow = fmap to8 . sqlSelectProcessRow

    from8P :: Proxy (a,b,c,d,e,f,g,h) -> Proxy ((a,b),(c,d),(e,f),(g,h))
    from8P = const Proxy

    to8 :: ((a,b),(c,d),(e,f),(g,h)) -> (a,b,c,d,e,f,g,h)
    to8 ((a,b),(c,d),(e,f),(g,h)) = (a,b,c,d,e,f,g,h)

    instance ( SqlSelect a ra
             , SqlSelect b rb
             , SqlSelect c rc
             , SqlSelect d rd
             , SqlSelect e re
             , SqlSelect f rf
             , SqlSelect g rg
             , SqlSelect h rh
             , SqlSelect i ri
             ) => SqlSelect (a, b, c, d, e, f, g, h, i) (ra, rb, rc, rd, re, rf, rg, rh, ri) where
      sqlSelectCols esc (a, b, c, d, e, f, g, h, i) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          , sqlSelectCols esc c
          , sqlSelectCols esc d
          , sqlSelectCols esc e
          , sqlSelectCols esc f
          , sqlSelectCols esc g
          , sqlSelectCols esc h
          , sqlSelectCols esc i
          ]
      sqlSelectColCount   = sqlSelectColCount . from9P
      sqlSelectProcessRow = fmap to9 . sqlSelectProcessRow

    from9P :: Proxy (a,b,c,d,e,f,g,h,i) -> Proxy ((a,b),(c,d),(e,f),(g,h),i)
    from9P = const Proxy

    to9 :: ((a,b),(c,d),(e,f),(g,h),i) -> (a,b,c,d,e,f,g,h,i)
    to9 ((a,b),(c,d),(e,f),(g,h),i) = (a,b,c,d,e,f,g,h,i)

    instance ( SqlSelect a ra
             , SqlSelect b rb
             , SqlSelect c rc
             , SqlSelect d rd
             , SqlSelect e re
             , SqlSelect f rf
             , SqlSelect g rg
             , SqlSelect h rh
             , SqlSelect i ri
             , SqlSelect j rj
             ) => SqlSelect (a, b, c, d, e, f, g, h, i, j) (ra, rb, rc, rd, re, rf, rg, rh, ri, rj) where
      sqlSelectCols esc (a, b, c, d, e, f, g, h, i, j) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          , sqlSelectCols esc c
          , sqlSelectCols esc d
          , sqlSelectCols esc e
          , sqlSelectCols esc f
          , sqlSelectCols esc g
          , sqlSelectCols esc h
          , sqlSelectCols esc i
          , sqlSelectCols esc j
          ]
      sqlSelectColCount   = sqlSelectColCount . from10P
      sqlSelectProcessRow = fmap to10 . sqlSelectProcessRow

    from10P :: Proxy (a,b,c,d,e,f,g,h,i,j) -> Proxy ((a,b),(c,d),(e,f),(g,h),(i,j))
    from10P = const Proxy

    to10 :: ((a,b),(c,d),(e,f),(g,h),(i,j)) -> (a,b,c,d,e,f,g,h,i,j)
    to10 ((a,b),(c,d),(e,f),(g,h),(i,j)) = (a,b,c,d,e,f,g,h,i,j)

    instance ( SqlSelect a ra
             , SqlSelect b rb
             , SqlSelect c rc
             , SqlSelect d rd
             , SqlSelect e re
             , SqlSelect f rf
             , SqlSelect g rg
             , SqlSelect h rh
             , SqlSelect i ri
             , SqlSelect j rj
             , SqlSelect k rk
             ) => SqlSelect (a, b, c, d, e, f, g, h, i, j, k) (ra, rb, rc, rd, re, rf, rg, rh, ri, rj, rk) where
      sqlSelectCols esc (a, b, c, d, e, f, g, h, i, j, k) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          , sqlSelectCols esc c
          , sqlSelectCols esc d
          , sqlSelectCols esc e
          , sqlSelectCols esc f
          , sqlSelectCols esc g
          , sqlSelectCols esc h
          , sqlSelectCols esc i
          , sqlSelectCols esc j
          , sqlSelectCols esc k
          ]
      sqlSelectColCount   = sqlSelectColCount . from11P
      sqlSelectProcessRow = fmap to11 . sqlSelectProcessRow

    from11P :: Proxy (a,b,c,d,e,f,g,h,i,j,k) -> Proxy ((a,b),(c,d),(e,f),(g,h),(i,j),k)
    from11P = const Proxy

    to11 :: ((a,b),(c,d),(e,f),(g,h),(i,j),k) -> (a,b,c,d,e,f,g,h,i,j,k)
    to11 ((a,b),(c,d),(e,f),(g,h),(i,j),k) = (a,b,c,d,e,f,g,h,i,j,k)

    instance ( SqlSelect a ra
             , SqlSelect b rb
             , SqlSelect c rc
             , SqlSelect d rd
             , SqlSelect e re
             , SqlSelect f rf
             , SqlSelect g rg
             , SqlSelect h rh
             , SqlSelect i ri
             , SqlSelect j rj
             , SqlSelect k rk
             , SqlSelect l rl
             ) => SqlSelect (a, b, c, d, e, f, g, h, i, j, k, l) (ra, rb, rc, rd, re, rf, rg, rh, ri, rj, rk, rl) where
      sqlSelectCols esc (a, b, c, d, e, f, g, h, i, j, k, l) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          , sqlSelectCols esc c
          , sqlSelectCols esc d
          , sqlSelectCols esc e
          , sqlSelectCols esc f
          , sqlSelectCols esc g
          , sqlSelectCols esc h
          , sqlSelectCols esc i
          , sqlSelectCols esc j
          , sqlSelectCols esc k
          , sqlSelectCols esc l
          ]
      sqlSelectColCount   = sqlSelectColCount . from12P
      sqlSelectProcessRow = fmap to12 . sqlSelectProcessRow

    from12P :: Proxy (a,b,c,d,e,f,g,h,i,j,k,l) -> Proxy ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l))
    from12P = const Proxy

    to12 :: ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l)) -> (a,b,c,d,e,f,g,h,i,j,k,l)
    to12 ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l)) = (a,b,c,d,e,f,g,h,i,j,k,l)

    instance ( SqlSelect a ra
             , SqlSelect b rb
             , SqlSelect c rc
             , SqlSelect d rd
             , SqlSelect e re
             , SqlSelect f rf
             , SqlSelect g rg
             , SqlSelect h rh
             , SqlSelect i ri
             , SqlSelect j rj
             , SqlSelect k rk
             , SqlSelect l rl
             , SqlSelect m rm
             ) => SqlSelect (a, b, c, d, e, f, g, h, i, j, k, l, m) (ra, rb, rc, rd, re, rf, rg, rh, ri, rj, rk, rl, rm) where
      sqlSelectCols esc (a, b, c, d, e, f, g, h, i, j, k, l, m) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          , sqlSelectCols esc c
          , sqlSelectCols esc d
          , sqlSelectCols esc e
          , sqlSelectCols esc f
          , sqlSelectCols esc g
          , sqlSelectCols esc h
          , sqlSelectCols esc i
          , sqlSelectCols esc j
          , sqlSelectCols esc k
          , sqlSelectCols esc l
          , sqlSelectCols esc m
          ]
      sqlSelectColCount   = sqlSelectColCount . from13P
      sqlSelectProcessRow = fmap to13 . sqlSelectProcessRow

    from13P :: Proxy (a,b,c,d,e,f,g,h,i,j,k,l,m) -> Proxy ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),m)
    from13P = const Proxy

    to13 :: ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),m) -> (a,b,c,d,e,f,g,h,i,j,k,l,m)
    to13 ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),m) = (a,b,c,d,e,f,g,h,i,j,k,l,m)

    instance ( SqlSelect a ra
             , SqlSelect b rb
             , SqlSelect c rc
             , SqlSelect d rd
             , SqlSelect e re
             , SqlSelect f rf
             , SqlSelect g rg
             , SqlSelect h rh
             , SqlSelect i ri
             , SqlSelect j rj
             , SqlSelect k rk
             , SqlSelect l rl
             , SqlSelect m rm
             , SqlSelect n rn
             ) => SqlSelect (a, b, c, d, e, f, g, h, i, j, k, l, m, n) (ra, rb, rc, rd, re, rf, rg, rh, ri, rj, rk, rl, rm, rn) where
      sqlSelectCols esc (a, b, c, d, e, f, g, h, i, j, k, l, m, n) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          , sqlSelectCols esc c
          , sqlSelectCols esc d
          , sqlSelectCols esc e
          , sqlSelectCols esc f
          , sqlSelectCols esc g
          , sqlSelectCols esc h
          , sqlSelectCols esc i
          , sqlSelectCols esc j
          , sqlSelectCols esc k
          , sqlSelectCols esc l
          , sqlSelectCols esc m
          , sqlSelectCols esc n
          ]
      sqlSelectColCount   = sqlSelectColCount . from14P
      sqlSelectProcessRow = fmap to14 . sqlSelectProcessRow

    from14P :: Proxy (a,b,c,d,e,f,g,h,i,j,k,l,m,n) -> Proxy ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),(m,n))
    from14P = const Proxy

    to14 :: ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),(m,n)) -> (a,b,c,d,e,f,g,h,i,j,k,l,m,n)
    to14 ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),(m,n)) = (a,b,c,d,e,f,g,h,i,j,k,l,m,n)

    instance ( SqlSelect a ra
             , SqlSelect b rb
             , SqlSelect c rc
             , SqlSelect d rd
             , SqlSelect e re
             , SqlSelect f rf
             , SqlSelect g rg
             , SqlSelect h rh
             , SqlSelect i ri
             , SqlSelect j rj
             , SqlSelect k rk
             , SqlSelect l rl
             , SqlSelect m rm
             , SqlSelect n rn
             , SqlSelect o ro
             ) => SqlSelect (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) (ra, rb, rc, rd, re, rf, rg, rh, ri, rj, rk, rl, rm, rn, ro) where
      sqlSelectCols esc (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          , sqlSelectCols esc c
          , sqlSelectCols esc d
          , sqlSelectCols esc e
          , sqlSelectCols esc f
          , sqlSelectCols esc g
          , sqlSelectCols esc h
          , sqlSelectCols esc i
          , sqlSelectCols esc j
          , sqlSelectCols esc k
          , sqlSelectCols esc l
          , sqlSelectCols esc m
          , sqlSelectCols esc n
          , sqlSelectCols esc o
          ]
      sqlSelectColCount   = sqlSelectColCount . from15P
      sqlSelectProcessRow = fmap to15 . sqlSelectProcessRow

    from15P :: Proxy (a,b,c,d,e,f,g,h,i,j,k,l,m,n, o) -> Proxy ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),(m,n),o)
    from15P = const Proxy

    to15 :: ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),(m,n),o) -> (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o)
    to15 ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),(m,n),o) = (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o)

    instance ( SqlSelect a ra
             , SqlSelect b rb
             , SqlSelect c rc
             , SqlSelect d rd
             , SqlSelect e re
             , SqlSelect f rf
             , SqlSelect g rg
             , SqlSelect h rh
             , SqlSelect i ri
             , SqlSelect j rj
             , SqlSelect k rk
             , SqlSelect l rl
             , SqlSelect m rm
             , SqlSelect n rn
             , SqlSelect o ro
             , SqlSelect p rp
             ) => SqlSelect (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) (ra, rb, rc, rd, re, rf, rg, rh, ri, rj, rk, rl, rm, rn, ro, rp) where
      sqlSelectCols esc (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) =
        uncommas'
          [ sqlSelectCols esc a
          , sqlSelectCols esc b
          , sqlSelectCols esc c
          , sqlSelectCols esc d
          , sqlSelectCols esc e
          , sqlSelectCols esc f
          , sqlSelectCols esc g
          , sqlSelectCols esc h
          , sqlSelectCols esc i
          , sqlSelectCols esc j
          , sqlSelectCols esc k
          , sqlSelectCols esc l
          , sqlSelectCols esc m
          , sqlSelectCols esc n
          , sqlSelectCols esc o
          , sqlSelectCols esc p
          ]
      sqlSelectColCount   = sqlSelectColCount . from16P
      sqlSelectProcessRow = fmap to16 . sqlSelectProcessRow

    from16P :: Proxy (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) -> Proxy ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),(m,n),(o,p))
    from16P = const Proxy

    to16 :: ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),(m,n),(o,p)) -> (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p)
    to16 ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),(m,n),(o,p)) = (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p)

    -- | Insert a 'PersistField' for every selected value.
    insertSelect :: (MonadLogger m, MonadResourceBase m, SqlSelect (SqlExpr (Insertion a)) r, PersistEntity a) =>
      SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m ()
    insertSelect = insertGeneralSelect SELECT

    -- | Insert a 'PersistField' for every unique selected value.
    insertSelectDistinct :: (MonadLogger m, MonadResourceBase m, SqlSelect (SqlExpr (Insertion a)) r, PersistEntity a) =>
      SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m ()
    insertSelectDistinct = insertGeneralSelect SELECT_DISTINCT

    insertGeneralSelect :: (MonadLogger m, MonadResourceBase m, SqlSelect (SqlExpr (Insertion a)) r, PersistEntity a) =>
      Mode -> SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m ()
    insertGeneralSelect mode query = do
      conn <- SqlPersistT R.ask
      uncurry rawExecute $ first builderToText $ toRawSql mode insertQuery (conn, initialIdentState) query