{-# LANGUAGE BangPatterns, TypeFamilies, DataKinds, PolyKinds, TypeApplications, ScopedTypeVariables, TypeInType, ConstraintKinds, TypeOperators, GADTs, UndecidableInstances, StandaloneDeriving, FunctionalDependencies, FlexibleContexts, InstanceSigs, AllowAmbiguousTypes, DeriveAnyClass #-}
{-|
Module: IHP.QueryBuilder
Description:  Tool to build simple sql queries
Copyright: (c) digitally induced GmbH, 2020

QueryBuilder is mainly used for doing simple `SELECT` sql queries. It allows dynamic
creation of sql queries in a type safe way.

For more complex sql queries, use 'IHP.ModelSupport.sqlQuery'.
-}
module IHP.QueryBuilder
( query
, filterWhere
, QueryBuilder (..)
, In (In)
, orderBy
, orderByAsc
, orderByDesc
, limit
, offset
, queryUnion
, queryOr
, DefaultScope (..)
, filterWhereIn
, filterWhereNotIn
, filterWhereLike
, filterWhereILike
, filterWhereMatches
, filterWhereIMatches
, EqOrIsOperator
, filterWhereSql
, FilterPrimaryKey (..)
, distinctOn
, distinct
, toSQL
, toSQL'
, buildQuery
)
where

import IHP.Prelude
import Database.PostgreSQL.Simple (Connection)
import Database.PostgreSQL.Simple.Types (Query (Query), In (In))
import Database.PostgreSQL.Simple.FromField hiding (Field, name)
import Database.PostgreSQL.Simple.ToField
import qualified Database.PostgreSQL.Simple as PG
import qualified Database.PostgreSQL.Simple.Types as PG
import GHC.OverloadedLabels
import IHP.ModelSupport
import qualified Data.ByteString.Builder as Builder
import IHP.HtmlSupport.ToHtml
import qualified Data.ByteString.Char8 as ByteString
import qualified Data.ByteString.Lazy as LByteString
import qualified Control.DeepSeq as DeepSeq
import qualified Data.Text.Encoding as Text

class DefaultScope table where
    defaultScope :: QueryBuilder table -> QueryBuilder table

instance {-# OVERLAPPABLE #-} DefaultScope table where
    {-# INLINE defaultScope #-}
    defaultScope :: QueryBuilder table -> QueryBuilder table
defaultScope QueryBuilder table
queryBuilder = QueryBuilder table
queryBuilder

instance Default (QueryBuilder table) where
    {-# INLINE def #-}
    def :: QueryBuilder table
def = QueryBuilder table
forall (table :: Symbol). QueryBuilder table
NewQueryBuilder

data MatchSensitivity = CaseSensitive | CaseInsensitive deriving (Int -> MatchSensitivity -> ShowS
[MatchSensitivity] -> ShowS
MatchSensitivity -> String
(Int -> MatchSensitivity -> ShowS)
-> (MatchSensitivity -> String)
-> ([MatchSensitivity] -> ShowS)
-> Show MatchSensitivity
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [MatchSensitivity] -> ShowS
$cshowList :: [MatchSensitivity] -> ShowS
show :: MatchSensitivity -> String
$cshow :: MatchSensitivity -> String
showsPrec :: Int -> MatchSensitivity -> ShowS
$cshowsPrec :: Int -> MatchSensitivity -> ShowS
Show, MatchSensitivity -> MatchSensitivity -> Bool
(MatchSensitivity -> MatchSensitivity -> Bool)
-> (MatchSensitivity -> MatchSensitivity -> Bool)
-> Eq MatchSensitivity
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: MatchSensitivity -> MatchSensitivity -> Bool
$c/= :: MatchSensitivity -> MatchSensitivity -> Bool
== :: MatchSensitivity -> MatchSensitivity -> Bool
$c== :: MatchSensitivity -> MatchSensitivity -> Bool
Eq)

data FilterOperator = EqOp | InOp | NotInOp | IsOp | LikeOp !MatchSensitivity | MatchesOp !MatchSensitivity | SqlOp deriving (Int -> FilterOperator -> ShowS
[FilterOperator] -> ShowS
FilterOperator -> String
(Int -> FilterOperator -> ShowS)
-> (FilterOperator -> String)
-> ([FilterOperator] -> ShowS)
-> Show FilterOperator
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [FilterOperator] -> ShowS
$cshowList :: [FilterOperator] -> ShowS
show :: FilterOperator -> String
$cshow :: FilterOperator -> String
showsPrec :: Int -> FilterOperator -> ShowS
$cshowsPrec :: Int -> FilterOperator -> ShowS
Show, FilterOperator -> FilterOperator -> Bool
(FilterOperator -> FilterOperator -> Bool)
-> (FilterOperator -> FilterOperator -> Bool) -> Eq FilterOperator
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: FilterOperator -> FilterOperator -> Bool
$c/= :: FilterOperator -> FilterOperator -> Bool
== :: FilterOperator -> FilterOperator -> Bool
$c== :: FilterOperator -> FilterOperator -> Bool
Eq)

{-# INLINE compileOperator #-}
compileOperator :: FilterOperator -> p
compileOperator FilterOperator
EqOp = p
"="
compileOperator FilterOperator
InOp = p
"IN"
compileOperator FilterOperator
NotInOp = p
"NOT IN"
compileOperator FilterOperator
IsOp = p
"IS"
compileOperator (LikeOp MatchSensitivity
CaseSensitive) = p
"LIKE"
compileOperator (LikeOp MatchSensitivity
CaseInsensitive) = p
"ILIKE"
compileOperator (MatchesOp MatchSensitivity
CaseSensitive) = p
"~"
compileOperator (MatchesOp MatchSensitivity
CaseInsensitive) = p
"~*"
compileOperator FilterOperator
SqlOp = p
""

data OrderByClause =
    OrderByClause
    { OrderByClause -> ByteString
orderByColumn :: !ByteString
    , OrderByClause -> OrderByDirection
orderByDirection :: !OrderByDirection }
    deriving (Int -> OrderByClause -> ShowS
[OrderByClause] -> ShowS
OrderByClause -> String
(Int -> OrderByClause -> ShowS)
-> (OrderByClause -> String)
-> ([OrderByClause] -> ShowS)
-> Show OrderByClause
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [OrderByClause] -> ShowS
$cshowList :: [OrderByClause] -> ShowS
show :: OrderByClause -> String
$cshow :: OrderByClause -> String
showsPrec :: Int -> OrderByClause -> ShowS
$cshowsPrec :: Int -> OrderByClause -> ShowS
Show, OrderByClause -> OrderByClause -> Bool
(OrderByClause -> OrderByClause -> Bool)
-> (OrderByClause -> OrderByClause -> Bool) -> Eq OrderByClause
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: OrderByClause -> OrderByClause -> Bool
$c/= :: OrderByClause -> OrderByClause -> Bool
== :: OrderByClause -> OrderByClause -> Bool
$c== :: OrderByClause -> OrderByClause -> Bool
Eq)

data QueryBuilder (table :: Symbol) =
    NewQueryBuilder
    | DistinctQueryBuilder   { QueryBuilder table -> QueryBuilder table
queryBuilder :: !(QueryBuilder table) }
    | DistinctOnQueryBuilder { queryBuilder :: !(QueryBuilder table), QueryBuilder table -> ByteString
distinctOnColumn :: !ByteString }
    | FilterByQueryBuilder   { queryBuilder :: !(QueryBuilder table), QueryBuilder table -> (ByteString, FilterOperator, Action)
queryFilter :: !(ByteString, FilterOperator, Action) }
    | OrderByQueryBuilder    { queryBuilder :: !(QueryBuilder table), QueryBuilder table -> OrderByClause
queryOrderByClause :: !OrderByClause }
    | LimitQueryBuilder      { queryBuilder :: !(QueryBuilder table), QueryBuilder table -> Int
queryLimit :: !Int }
    | OffsetQueryBuilder     { queryBuilder :: !(QueryBuilder table), QueryBuilder table -> Int
queryOffset :: !Int }
    | UnionQueryBuilder      { QueryBuilder table -> QueryBuilder table
firstQueryBuilder :: !(QueryBuilder table), QueryBuilder table -> QueryBuilder table
secondQueryBuilder :: !(QueryBuilder table) }
    deriving (Int -> QueryBuilder table -> ShowS
[QueryBuilder table] -> ShowS
QueryBuilder table -> String
(Int -> QueryBuilder table -> ShowS)
-> (QueryBuilder table -> String)
-> ([QueryBuilder table] -> ShowS)
-> Show (QueryBuilder table)
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
forall (table :: Symbol). Int -> QueryBuilder table -> ShowS
forall (table :: Symbol). [QueryBuilder table] -> ShowS
forall (table :: Symbol). QueryBuilder table -> String
showList :: [QueryBuilder table] -> ShowS
$cshowList :: forall (table :: Symbol). [QueryBuilder table] -> ShowS
show :: QueryBuilder table -> String
$cshow :: forall (table :: Symbol). QueryBuilder table -> String
showsPrec :: Int -> QueryBuilder table -> ShowS
$cshowsPrec :: forall (table :: Symbol). Int -> QueryBuilder table -> ShowS
Show, QueryBuilder table -> QueryBuilder table -> Bool
(QueryBuilder table -> QueryBuilder table -> Bool)
-> (QueryBuilder table -> QueryBuilder table -> Bool)
-> Eq (QueryBuilder table)
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall (table :: Symbol).
QueryBuilder table -> QueryBuilder table -> Bool
/= :: QueryBuilder table -> QueryBuilder table -> Bool
$c/= :: forall (table :: Symbol).
QueryBuilder table -> QueryBuilder table -> Bool
== :: QueryBuilder table -> QueryBuilder table -> Bool
$c== :: forall (table :: Symbol).
QueryBuilder table -> QueryBuilder table -> Bool
Eq)

data Condition = VarCondition !ByteString !Action | OrCondition !Condition !Condition | AndCondition !Condition !Condition deriving (Int -> Condition -> ShowS
[Condition] -> ShowS
Condition -> String
(Int -> Condition -> ShowS)
-> (Condition -> String)
-> ([Condition] -> ShowS)
-> Show Condition
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Condition] -> ShowS
$cshowList :: [Condition] -> ShowS
show :: Condition -> String
$cshow :: Condition -> String
showsPrec :: Int -> Condition -> ShowS
$cshowsPrec :: Int -> Condition -> ShowS
Show, Condition -> Condition -> Bool
(Condition -> Condition -> Bool)
-> (Condition -> Condition -> Bool) -> Eq Condition
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Condition -> Condition -> Bool
$c/= :: Condition -> Condition -> Bool
== :: Condition -> Condition -> Bool
$c== :: Condition -> Condition -> Bool
Eq)

-- | Display QueryBuilder's as their sql query inside HSX
instance KnownSymbol table => ToHtml (QueryBuilder table) where
    toHtml :: QueryBuilder table -> Html
toHtml QueryBuilder table
queryBuilder = (ByteString, [Action]) -> Html
forall a. ToHtml a => a -> Html
toHtml (QueryBuilder table -> (ByteString, [Action])
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> (ByteString, [Action])
toSQL QueryBuilder table
queryBuilder)

data OrderByDirection = Asc | Desc deriving (OrderByDirection -> OrderByDirection -> Bool
(OrderByDirection -> OrderByDirection -> Bool)
-> (OrderByDirection -> OrderByDirection -> Bool)
-> Eq OrderByDirection
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: OrderByDirection -> OrderByDirection -> Bool
$c/= :: OrderByDirection -> OrderByDirection -> Bool
== :: OrderByDirection -> OrderByDirection -> Bool
$c== :: OrderByDirection -> OrderByDirection -> Bool
Eq, Int -> OrderByDirection -> ShowS
[OrderByDirection] -> ShowS
OrderByDirection -> String
(Int -> OrderByDirection -> ShowS)
-> (OrderByDirection -> String)
-> ([OrderByDirection] -> ShowS)
-> Show OrderByDirection
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [OrderByDirection] -> ShowS
$cshowList :: [OrderByDirection] -> ShowS
show :: OrderByDirection -> String
$cshow :: OrderByDirection -> String
showsPrec :: Int -> OrderByDirection -> ShowS
$cshowsPrec :: Int -> OrderByDirection -> ShowS
Show)
data SQLQuery = SQLQuery
    { SQLQuery -> ByteString
selectFrom :: !ByteString
    , SQLQuery -> Maybe ByteString
distinctClause :: !(Maybe ByteString)
    , SQLQuery -> Maybe ByteString
distinctOnClause :: !(Maybe ByteString)
    , SQLQuery -> Maybe Condition
whereCondition :: !(Maybe Condition)
    , SQLQuery -> [OrderByClause]
orderByClause :: ![OrderByClause]
    , SQLQuery -> Maybe ByteString
limitClause :: !(Maybe ByteString)
    , SQLQuery -> Maybe ByteString
offsetClause :: !(Maybe ByteString)
    } deriving (Int -> SQLQuery -> ShowS
[SQLQuery] -> ShowS
SQLQuery -> String
(Int -> SQLQuery -> ShowS)
-> (SQLQuery -> String) -> ([SQLQuery] -> ShowS) -> Show SQLQuery
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [SQLQuery] -> ShowS
$cshowList :: [SQLQuery] -> ShowS
show :: SQLQuery -> String
$cshow :: SQLQuery -> String
showsPrec :: Int -> SQLQuery -> ShowS
$cshowsPrec :: Int -> SQLQuery -> ShowS
Show, SQLQuery -> SQLQuery -> Bool
(SQLQuery -> SQLQuery -> Bool)
-> (SQLQuery -> SQLQuery -> Bool) -> Eq SQLQuery
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: SQLQuery -> SQLQuery -> Bool
$c/= :: SQLQuery -> SQLQuery -> Bool
== :: SQLQuery -> SQLQuery -> Bool
$c== :: SQLQuery -> SQLQuery -> Bool
Eq)

-- | Needed for the 'Eq QueryBuilder' instance
deriving instance Eq Action

-- | Need for the 'Eq QueryBuilder' instance
--
-- You likely wonder: Why do we need the 'Eq SQLQuery' instance if this causes so much trouble?
-- This has to do with how has-many and belongs-to relations are models by the SchemaCompiler
--
-- E.g. given a table users and a table posts. Each Post belongs to a user. The schema compiler will
-- add a field 'posts :: QueryBuilder "posts"' with the default value @query |> filterWhere (#userId, get #id self)@ to all users by default.
-- 
-- This is needed to support syntax like this:
-- 
-- > user
-- >     |> get #posts
-- >     |> fetch
--
instance Eq Builder.Builder where
    Builder
a == :: Builder -> Builder -> Bool
== Builder
b = (Builder -> ByteString
Builder.toLazyByteString Builder
a) ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== (Builder -> ByteString
Builder.toLazyByteString Builder
b)

instance SetField "selectFrom" SQLQuery ByteString where setField :: ByteString -> SQLQuery -> SQLQuery
setField ByteString
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { $sel:selectFrom:SQLQuery :: ByteString
selectFrom = ByteString
value }
instance SetField "distinctClause" SQLQuery (Maybe ByteString) where setField :: Maybe ByteString -> SQLQuery -> SQLQuery
setField Maybe ByteString
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { $sel:distinctClause:SQLQuery :: Maybe ByteString
distinctClause = Maybe ByteString
value }
instance SetField "distinctOnClause" SQLQuery (Maybe ByteString) where setField :: Maybe ByteString -> SQLQuery -> SQLQuery
setField Maybe ByteString
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { $sel:distinctOnClause:SQLQuery :: Maybe ByteString
distinctOnClause = Maybe ByteString
value }
instance SetField "whereCondition" SQLQuery (Maybe Condition) where setField :: Maybe Condition -> SQLQuery -> SQLQuery
setField Maybe Condition
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { $sel:whereCondition:SQLQuery :: Maybe Condition
whereCondition = Maybe Condition
value }
instance SetField "orderByClause" SQLQuery [OrderByClause] where setField :: [OrderByClause] -> SQLQuery -> SQLQuery
setField [OrderByClause]
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { $sel:orderByClause:SQLQuery :: [OrderByClause]
orderByClause = [OrderByClause]
value }
instance SetField "limitClause" SQLQuery (Maybe ByteString) where setField :: Maybe ByteString -> SQLQuery -> SQLQuery
setField Maybe ByteString
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { $sel:limitClause:SQLQuery :: Maybe ByteString
limitClause = Maybe ByteString
value }
instance SetField "offsetClause" SQLQuery (Maybe ByteString) where setField :: Maybe ByteString -> SQLQuery -> SQLQuery
setField Maybe ByteString
value SQLQuery
sqlQuery = SQLQuery
sqlQuery { $sel:offsetClause:SQLQuery :: Maybe ByteString
offsetClause = Maybe ByteString
value }



-- | Represent's a @SELECT * FROM ..@ query. It's the starting point to build a query.
-- Used together with the other functions to compose a sql query.
--
-- Example:
--
-- > toSQL (query @User)
-- > -- Returns: ("SELECT id, firstname, lastname FROM users", [])
--
-- Example: Fetching all users
--
-- > allUsers <- query @User |> fetch
-- > -- Runs a 'SELECT * FROM users' query
--
-- You can use it together with 'filterWhere':
--
-- > activeUsers :: [User] <-
-- >    query @User
-- >     |> filterWhere (#active, True)
-- >     |> fetch
query :: forall model table. (table ~ GetTableName model) => DefaultScope table => QueryBuilder table
query :: QueryBuilder table
query = (QueryBuilder table -> QueryBuilder table
forall (table :: Symbol).
DefaultScope table =>
QueryBuilder table -> QueryBuilder table
defaultScope @table) QueryBuilder table
forall (table :: Symbol). QueryBuilder table
NewQueryBuilder
{-# INLINE query #-}

{-# INLINE buildQuery #-}
buildQuery :: forall table. (KnownSymbol table) => QueryBuilder table -> SQLQuery
buildQuery :: QueryBuilder table -> SQLQuery
buildQuery QueryBuilder table
NewQueryBuilder =
    let tableName :: ByteString
tableName = KnownSymbol table => ByteString
forall (symbol :: Symbol). KnownSymbol symbol => ByteString
symbolToByteString @table
    in SQLQuery :: ByteString
-> Maybe ByteString
-> Maybe ByteString
-> Maybe Condition
-> [OrderByClause]
-> Maybe ByteString
-> Maybe ByteString
-> SQLQuery
SQLQuery
            { $sel:selectFrom:SQLQuery :: ByteString
selectFrom = ByteString
tableName
            , $sel:distinctClause:SQLQuery :: Maybe ByteString
distinctClause = Maybe ByteString
forall a. Maybe a
Nothing
            , $sel:distinctOnClause:SQLQuery :: Maybe ByteString
distinctOnClause = Maybe ByteString
forall a. Maybe a
Nothing
            , $sel:whereCondition:SQLQuery :: Maybe Condition
whereCondition = Maybe Condition
forall a. Maybe a
Nothing
            , $sel:orderByClause:SQLQuery :: [OrderByClause]
orderByClause = []
            , $sel:limitClause:SQLQuery :: Maybe ByteString
limitClause = Maybe ByteString
forall a. Maybe a
Nothing
            , $sel:offsetClause:SQLQuery :: Maybe ByteString
offsetClause = Maybe ByteString
forall a. Maybe a
Nothing
            }
buildQuery DistinctQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
queryBuilder } = QueryBuilder table
queryBuilder
        QueryBuilder table -> (QueryBuilder table -> SQLQuery) -> SQLQuery
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> QueryBuilder table -> SQLQuery
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery
        SQLQuery -> (SQLQuery -> SQLQuery) -> SQLQuery
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> Proxy "distinctClause" -> ByteString -> SQLQuery -> SQLQuery
forall model (name :: Symbol) value.
(KnownSymbol name, SetField name model (Maybe value)) =>
Proxy name -> value -> model -> model
setJust IsLabel "distinctClause" (Proxy "distinctClause")
Proxy "distinctClause"
#distinctClause ByteString
"DISTINCT"
buildQuery DistinctOnQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
queryBuilder, ByteString
distinctOnColumn :: ByteString
$sel:distinctOnColumn:NewQueryBuilder :: forall (table :: Symbol). QueryBuilder table -> ByteString
distinctOnColumn } = QueryBuilder table
queryBuilder
        QueryBuilder table -> (QueryBuilder table -> SQLQuery) -> SQLQuery
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> QueryBuilder table -> SQLQuery
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery
        SQLQuery -> (SQLQuery -> SQLQuery) -> SQLQuery
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> Proxy "distinctOnClause" -> ByteString -> SQLQuery -> SQLQuery
forall model (name :: Symbol) value.
(KnownSymbol name, SetField name model (Maybe value)) =>
Proxy name -> value -> model -> model
setJust IsLabel "distinctOnClause" (Proxy "distinctOnClause")
Proxy "distinctOnClause"
#distinctOnClause (ByteString
"DISTINCT ON (" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
distinctOnColumn ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
")")
buildQuery FilterByQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
queryBuilder, $sel:queryFilter:NewQueryBuilder :: forall (table :: Symbol).
QueryBuilder table -> (ByteString, FilterOperator, Action)
queryFilter = (ByteString
columnName, FilterOperator
operator, Action
value) } =
            let
                condition :: Condition
condition = ByteString -> Action -> Condition
VarCondition (ByteString
columnName ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
" " ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> FilterOperator -> ByteString
forall p. IsString p => FilterOperator -> p
compileOperator FilterOperator
operator ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
" ?") Action
value
            in
                QueryBuilder table
queryBuilder
                    QueryBuilder table -> (QueryBuilder table -> SQLQuery) -> SQLQuery
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> QueryBuilder table -> SQLQuery
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery
                    SQLQuery -> (SQLQuery -> SQLQuery) -> SQLQuery
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> Proxy "whereCondition"
-> (Maybe Condition -> Maybe Condition) -> SQLQuery -> SQLQuery
forall k model (name :: Symbol) value (updateFunction :: k).
(KnownSymbol name, HasField name model value,
 SetField name model value) =>
Proxy name -> (value -> value) -> model -> model
modify IsLabel "whereCondition" (Proxy "whereCondition")
Proxy "whereCondition"
#whereCondition \case
                            Just Condition
c -> Condition -> Maybe Condition
forall a. a -> Maybe a
Just (Condition -> Condition -> Condition
AndCondition Condition
c Condition
condition)
                            Maybe Condition
Nothing -> Condition -> Maybe Condition
forall a. a -> Maybe a
Just Condition
condition
buildQuery OrderByQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
queryBuilder, OrderByClause
queryOrderByClause :: OrderByClause
$sel:queryOrderByClause:NewQueryBuilder :: forall (table :: Symbol). QueryBuilder table -> OrderByClause
queryOrderByClause } = QueryBuilder table
queryBuilder
        QueryBuilder table -> (QueryBuilder table -> SQLQuery) -> SQLQuery
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> QueryBuilder table -> SQLQuery
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery
        SQLQuery -> (SQLQuery -> SQLQuery) -> SQLQuery
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> Proxy "orderByClause"
-> ([OrderByClause] -> [OrderByClause]) -> SQLQuery -> SQLQuery
forall k model (name :: Symbol) value (updateFunction :: k).
(KnownSymbol name, HasField name model value,
 SetField name model value) =>
Proxy name -> (value -> value) -> model -> model
modify IsLabel "orderByClause" (Proxy "orderByClause")
Proxy "orderByClause"
#orderByClause (\[OrderByClause]
value -> [OrderByClause]
value [OrderByClause] -> [OrderByClause] -> [OrderByClause]
forall a. Semigroup a => a -> a -> a
<> [OrderByClause
queryOrderByClause] ) -- although adding to the end of a list is bad form, these lists are very short
buildQuery LimitQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
queryBuilder, Int
queryLimit :: Int
$sel:queryLimit:NewQueryBuilder :: forall (table :: Symbol). QueryBuilder table -> Int
queryLimit } =
                QueryBuilder table
queryBuilder
                QueryBuilder table -> (QueryBuilder table -> SQLQuery) -> SQLQuery
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> QueryBuilder table -> SQLQuery
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery
                SQLQuery -> (SQLQuery -> SQLQuery) -> SQLQuery
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> Proxy "limitClause" -> ByteString -> SQLQuery -> SQLQuery
forall model (name :: Symbol) value.
(KnownSymbol name, SetField name model (Maybe value)) =>
Proxy name -> value -> model -> model
setJust IsLabel "limitClause" (Proxy "limitClause")
Proxy "limitClause"
#limitClause (
                        (ByteString -> Builder
Builder.byteString ByteString
"LIMIT " Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Int -> Builder
Builder.intDec Int
queryLimit)
                        Builder -> (Builder -> ByteString) -> ByteString
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> Builder -> ByteString
Builder.toLazyByteString
                        ByteString -> (ByteString -> ByteString) -> ByteString
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> ByteString -> ByteString
LByteString.toStrict
                    )
buildQuery OffsetQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
queryBuilder, Int
queryOffset :: Int
$sel:queryOffset:NewQueryBuilder :: forall (table :: Symbol). QueryBuilder table -> Int
queryOffset } = QueryBuilder table
queryBuilder
        QueryBuilder table -> (QueryBuilder table -> SQLQuery) -> SQLQuery
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> QueryBuilder table -> SQLQuery
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery
        SQLQuery -> (SQLQuery -> SQLQuery) -> SQLQuery
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> Proxy "offsetClause" -> ByteString -> SQLQuery -> SQLQuery
forall model (name :: Symbol) value.
(KnownSymbol name, SetField name model (Maybe value)) =>
Proxy name -> value -> model -> model
setJust IsLabel "offsetClause" (Proxy "offsetClause")
Proxy "offsetClause"
#offsetClause (
                (ByteString -> Builder
Builder.byteString ByteString
"OFFSET " Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Int -> Builder
Builder.intDec Int
queryOffset)
                Builder -> (Builder -> ByteString) -> ByteString
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> Builder -> ByteString
Builder.toLazyByteString
                ByteString -> (ByteString -> ByteString) -> ByteString
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> ByteString -> ByteString
LByteString.toStrict
            )
buildQuery UnionQueryBuilder { QueryBuilder table
firstQueryBuilder :: QueryBuilder table
$sel:firstQueryBuilder:NewQueryBuilder :: forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
firstQueryBuilder, QueryBuilder table
secondQueryBuilder :: QueryBuilder table
$sel:secondQueryBuilder:NewQueryBuilder :: forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
secondQueryBuilder } =
            let
                firstQuery :: SQLQuery
firstQuery = QueryBuilder table -> SQLQuery
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery QueryBuilder table
firstQueryBuilder
                secondQuery :: SQLQuery
secondQuery = QueryBuilder table -> SQLQuery
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery QueryBuilder table
secondQueryBuilder
                isSimpleQuery :: SQLQuery -> Bool
isSimpleQuery SQLQuery
query = [OrderByClause] -> Bool
forall mono. MonoFoldable mono => mono -> Bool
null (SQLQuery -> [OrderByClause]
orderByClause SQLQuery
query) Bool -> Bool -> Bool
&& Maybe ByteString -> Bool
forall a. Maybe a -> Bool
isNothing (SQLQuery -> Maybe ByteString
limitClause SQLQuery
query) Bool -> Bool -> Bool
&& Maybe ByteString -> Bool
forall a. Maybe a -> Bool
isNothing (SQLQuery -> Maybe ByteString
offsetClause SQLQuery
query)
                isSimpleUnion :: Bool
isSimpleUnion = SQLQuery -> Bool
isSimpleQuery SQLQuery
firstQuery Bool -> Bool -> Bool
&& SQLQuery -> Bool
isSimpleQuery SQLQuery
secondQuery
                unionWhere :: Maybe Condition
unionWhere =
                    case (SQLQuery -> Maybe Condition
whereCondition SQLQuery
firstQuery, SQLQuery -> Maybe Condition
whereCondition SQLQuery
secondQuery) of
                        (Maybe Condition
Nothing, Maybe Condition
whereCondition) -> Maybe Condition
whereCondition
                        (Maybe Condition
whereCondition, Maybe Condition
Nothing) -> Maybe Condition
whereCondition
                        (Just Condition
firstWhere, Just Condition
secondWhere) -> Condition -> Maybe Condition
forall a. a -> Maybe a
Just (Condition -> Maybe Condition) -> Condition -> Maybe Condition
forall a b. (a -> b) -> a -> b
$ Condition -> Condition -> Condition
OrCondition Condition
firstWhere Condition
secondWhere
            in
                if Bool
isSimpleUnion then
                    SQLQuery
firstQuery { $sel:whereCondition:SQLQuery :: Maybe Condition
whereCondition = Maybe Condition
unionWhere }
                else
                    Text -> SQLQuery
forall a. Text -> a
error Text
"buildQuery: Union of complex queries not supported yet"

-- | Transforms a @query @@User |> ..@ expression into a SQL Query. Returns a tuple with the sql query template and it's placeholder values.
--
-- __Example:__ Get the sql query that is represented by a QueryBuilder
--
-- >>> let postsQuery = query @Post |> filterWhere (#public, True)
-- >>> toSQL postsQuery
-- ("SELECT posts.* FROM posts WHERE public = ?", [Plain "true"])
toSQL :: (KnownSymbol table) => QueryBuilder table -> (ByteString, [Action])
toSQL :: QueryBuilder table -> (ByteString, [Action])
toSQL QueryBuilder table
queryBuilder = SQLQuery -> (ByteString, [Action])
toSQL' (QueryBuilder table -> SQLQuery
forall (table :: Symbol).
KnownSymbol table =>
QueryBuilder table -> SQLQuery
buildQuery QueryBuilder table
queryBuilder)
{-# INLINE toSQL #-}

toSQL' :: SQLQuery -> (ByteString, [Action])
toSQL' :: SQLQuery -> (ByteString, [Action])
toSQL' sqlQuery :: SQLQuery
sqlQuery@SQLQuery { ByteString
selectFrom :: ByteString
$sel:selectFrom:SQLQuery :: SQLQuery -> ByteString
selectFrom, Maybe ByteString
distinctClause :: Maybe ByteString
$sel:distinctClause:SQLQuery :: SQLQuery -> Maybe ByteString
distinctClause, Maybe ByteString
distinctOnClause :: Maybe ByteString
$sel:distinctOnClause:SQLQuery :: SQLQuery -> Maybe ByteString
distinctOnClause, [OrderByClause]
orderByClause :: [OrderByClause]
$sel:orderByClause:SQLQuery :: SQLQuery -> [OrderByClause]
orderByClause, Maybe ByteString
limitClause :: Maybe ByteString
$sel:limitClause:SQLQuery :: SQLQuery -> Maybe ByteString
limitClause, Maybe ByteString
offsetClause :: Maybe ByteString
$sel:offsetClause:SQLQuery :: SQLQuery -> Maybe ByteString
offsetClause } =
        (ByteString -> ByteString
forall a. NFData a => a -> a
DeepSeq.force ByteString
theQuery, [Action]
theParams)
    where
        !theQuery :: ByteString
theQuery =
            ByteString -> [ByteString] -> ByteString
ByteString.intercalate ByteString
" " ([ByteString] -> ByteString) -> [ByteString] -> ByteString
forall a b. (a -> b) -> a -> b
$
                [Maybe ByteString] -> [ByteString]
forall a. [Maybe a] -> [a]
catMaybes
                    [ ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just ByteString
"SELECT"
                    , Maybe ByteString
distinctClause
                    , Maybe ByteString
distinctOnClause
                    , ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just ByteString
selectors
                    , ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just ByteString
"FROM"
                    , ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just ByteString
fromClause
                    , Maybe ByteString
whereConditions'
                    , Maybe ByteString
orderByClause'
                    , Maybe ByteString
limitClause
                    , Maybe ByteString
offsetClause
                    ]

        selectors :: ByteString
        selectors :: ByteString
selectors = ByteString
selectFrom ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
".*"

        fromClause :: ByteString
        fromClause :: ByteString
fromClause = ByteString
selectFrom

        !theParams :: [Action]
theParams =
            case SQLQuery -> Maybe Condition
whereCondition SQLQuery
sqlQuery of
                Just Condition
condition -> Condition -> [Action]
compileConditionArgs Condition
condition
                Maybe Condition
Nothing -> [Action]
forall a. Monoid a => a
mempty

        toQualifiedName :: ByteString -> ByteString
toQualifiedName ByteString
unqualifiedName = ByteString
selectFrom ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
"." ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
unqualifiedName

        whereConditions' :: Maybe ByteString
whereConditions' = case SQLQuery -> Maybe Condition
whereCondition SQLQuery
sqlQuery of
                Just Condition
condition -> ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just (ByteString -> Maybe ByteString) -> ByteString -> Maybe ByteString
forall a b. (a -> b) -> a -> b
$ ByteString
"WHERE " ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Condition -> ByteString
compileConditionQuery Condition
condition
                Maybe Condition
Nothing -> Maybe ByteString
forall a. Maybe a
Nothing

        orderByClause' :: Maybe ByteString
        orderByClause' :: Maybe ByteString
orderByClause' = case [OrderByClause]
orderByClause of
                [] -> Maybe ByteString
forall a. Maybe a
Nothing
                [OrderByClause]
xs -> ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just (ByteString
"ORDER BY " ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString -> [ByteString] -> ByteString
ByteString.intercalate ByteString
"," (((OrderByClause -> ByteString) -> [OrderByClause] -> [ByteString]
forall a b. (a -> b) -> [a] -> [b]
map (\OrderByClause { ByteString
orderByColumn :: ByteString
$sel:orderByColumn:OrderByClause :: OrderByClause -> ByteString
orderByColumn, OrderByDirection
orderByDirection :: OrderByDirection
$sel:orderByDirection:OrderByClause :: OrderByClause -> OrderByDirection
orderByDirection } -> ByteString
orderByColumn ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> (if OrderByDirection
orderByDirection OrderByDirection -> OrderByDirection -> Bool
forall a. Eq a => a -> a -> Bool
== OrderByDirection
Desc then ByteString
" DESC" else ByteString
forall a. Monoid a => a
mempty)) [OrderByClause]
xs)))

{-# INLINE toSQL' #-}

{-# INLINE compileConditionQuery #-}
compileConditionQuery :: Condition -> ByteString
compileConditionQuery :: Condition -> ByteString
compileConditionQuery (VarCondition ByteString
var Action
_) =  ByteString
var
compileConditionQuery (OrCondition Condition
a Condition
b) =  ByteString
"(" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Condition -> ByteString
compileConditionQuery Condition
a ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
") OR (" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Condition -> ByteString
compileConditionQuery Condition
b ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
")"
compileConditionQuery (AndCondition Condition
a Condition
b) =  ByteString
"(" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Condition -> ByteString
compileConditionQuery Condition
a ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
") AND (" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Condition -> ByteString
compileConditionQuery Condition
b ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
")"

{-# INLINE compileConditionArgs #-}
compileConditionArgs :: Condition -> [Action]
compileConditionArgs :: Condition -> [Action]
compileConditionArgs (VarCondition ByteString
_ Action
arg) = [Action
arg]
compileConditionArgs (OrCondition Condition
a Condition
b) = Condition -> [Action]
compileConditionArgs Condition
a [Action] -> [Action] -> [Action]
forall a. Semigroup a => a -> a -> a
<> Condition -> [Action]
compileConditionArgs Condition
b
compileConditionArgs (AndCondition Condition
a Condition
b) = Condition -> [Action]
compileConditionArgs Condition
a [Action] -> [Action] -> [Action]
forall a. Semigroup a => a -> a -> a
<> Condition -> [Action]
compileConditionArgs Condition
b

class FilterPrimaryKey table where
    filterWhereId :: Id' table -> QueryBuilder table -> QueryBuilder table

-- | Adds a simple @WHERE x = y@ condition to the query.
--
-- __Example:__ Only show projects where @active@ is @True@.
--
-- > activeProjects <- query @Project
-- >     |> filterWhere (#active, True)
-- >     |> fetch
-- > -- SELECT * FROM projects WHERE active = True
--
-- __Example:__ Find book with title @Learn you a Haskell@.
--
-- > book <- query @Book
-- >     |> filterWhere (#title, "Learn you a Haskell")
-- >     |> fetchOne
-- > -- SELECT * FROM books WHERE name = 'Learn you a Haskell' LIMIT 1
--
--
-- __Example:__ Find active projects owned by the current user.
--
-- > projects <- query @User
-- >     |> filterWhere (#active, True)
-- >     |> filterWhere (#currentUserId, currentUserId)
-- >     |> fetch
-- > -- SELECT * FROM projects WHERE active = true AND current_user_id = '..'
--
--
-- For dynamic conditions (e.g. involving @NOW()@), see 'filterWhereSql'.
--
-- For @WHERE x IN (a, b, c)@ conditions, take a look at 'filterWhereIn' and 'filterWhereNotIn'.
--
-- For @WHERE x LIKE a@ or @WHERE x ~ a@  conditions, see 'filterWhereLike' and 'filterWhereMatches' respectively.
-- For case-insensitive versions of these operators, see 'filterWhereILike' and 'filterWhereIMatches'.
--
-- When your condition is too complex, use a raw sql query with 'IHP.ModelSupport.sqlQuery'.
filterWhere :: forall name table model value. (KnownSymbol name, ToField value, HasField name model value, EqOrIsOperator value, model ~ GetModelByTableName table) => (Proxy name, value) -> QueryBuilder table -> QueryBuilder table
filterWhere :: (Proxy name, value) -> QueryBuilder table -> QueryBuilder table
filterWhere (Proxy name
name, value
value) QueryBuilder table
queryBuilder = FilterByQueryBuilder :: forall (table :: Symbol).
QueryBuilder table
-> (ByteString, FilterOperator, Action) -> QueryBuilder table
FilterByQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: QueryBuilder table
queryBuilder, $sel:queryFilter:NewQueryBuilder :: (ByteString, FilterOperator, Action)
queryFilter = (ByteString
columnName, value -> FilterOperator
forall value. EqOrIsOperator value => value -> FilterOperator
toEqOrIsOperator value
value, value -> Action
forall a. ToField a => a -> Action
toField value
value) }
    where
        columnName :: ByteString
columnName = Text -> ByteString
Text.encodeUtf8 (Text -> Text
fieldNameToColumnName (KnownSymbol name => Text
forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name))
{-# INLINE filterWhere #-}

-- | Adds a @WHERE x IN (y)@ condition to the query.
--
-- __Example:__ Only show projects where @status@ is @Draft@ or @Active@.
--
-- > visibleProjects <- query @Project
-- >     |> filterWhereIn (#status, [Draft, Active])
-- >     |> fetch
-- > -- SELECT * FROM projects WHERE status IN ('draft', 'active')
--
-- For negation use 'filterWhereNotIn'
--
filterWhereIn :: forall name table model value. (KnownSymbol name, ToField value, HasField name model value, model ~ GetModelByTableName table) => (Proxy name, [value]) -> QueryBuilder table -> QueryBuilder table
filterWhereIn :: (Proxy name, [value]) -> QueryBuilder table -> QueryBuilder table
filterWhereIn (Proxy name
name, [value]
value) QueryBuilder table
queryBuilder = FilterByQueryBuilder :: forall (table :: Symbol).
QueryBuilder table
-> (ByteString, FilterOperator, Action) -> QueryBuilder table
FilterByQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: QueryBuilder table
queryBuilder, $sel:queryFilter:NewQueryBuilder :: (ByteString, FilterOperator, Action)
queryFilter = (ByteString
columnName, FilterOperator
InOp, In [value] -> Action
forall a. ToField a => a -> Action
toField ([value] -> In [value]
forall a. a -> In a
In [value]
value)) }
    where
        columnName :: ByteString
columnName = Text -> ByteString
Text.encodeUtf8 (Text -> Text
fieldNameToColumnName (KnownSymbol name => Text
forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name))
{-# INLINE filterWhereIn #-}

-- | Adds a @WHERE x NOT IN (y)@ condition to the query.
--
-- __Example:__ Only show projects where @status@ is not @Archived@
--
-- > visibleProjects <- query @Project
-- >     |> filterWhereNotIn (#status, [Archived])
-- >     |> fetch
-- > -- SELECT * FROM projects WHERE status NOT IN ('archived')
--
-- The inclusive version of this function is called 'filterWhereIn'.
--
filterWhereNotIn :: forall name table model value. (KnownSymbol name, ToField value, HasField name model value, model ~ GetModelByTableName table) => (Proxy name, [value]) -> QueryBuilder table -> QueryBuilder table
filterWhereNotIn :: (Proxy name, [value]) -> QueryBuilder table -> QueryBuilder table
filterWhereNotIn (Proxy name
_, []) QueryBuilder table
queryBuilder = QueryBuilder table
queryBuilder -- Handle empty case by ignoring query part: `WHERE x NOT IN ()`
filterWhereNotIn (Proxy name
name, [value]
value) QueryBuilder table
queryBuilder = FilterByQueryBuilder :: forall (table :: Symbol).
QueryBuilder table
-> (ByteString, FilterOperator, Action) -> QueryBuilder table
FilterByQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: QueryBuilder table
queryBuilder, $sel:queryFilter:NewQueryBuilder :: (ByteString, FilterOperator, Action)
queryFilter = (ByteString
columnName, FilterOperator
NotInOp, In [value] -> Action
forall a. ToField a => a -> Action
toField ([value] -> In [value]
forall a. a -> In a
In [value]
value)) }
    where
        columnName :: ByteString
columnName = Text -> ByteString
Text.encodeUtf8 (Text -> Text
fieldNameToColumnName (KnownSymbol name => Text
forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name))
{-# INLINE filterWhereNotIn #-}

-- | Adds a @WHERE x LIKE y@ condition to the query.
--
-- __Example:__ Find titles matching search term.
--
-- > articles <- query @Article
-- >     |> filterWhereLike (#title, "%" <> searchTerm <> "%")
-- >     |> fetch
-- > -- SELECT * FROM articles WHERE title LIKE '%..%'
filterWhereLike :: forall name table model value. (KnownSymbol name, ToField value, HasField name model value, model ~ GetModelByTableName table) => (Proxy name, value) -> QueryBuilder table -> QueryBuilder table
filterWhereLike :: (Proxy name, value) -> QueryBuilder table -> QueryBuilder table
filterWhereLike (Proxy name
name, value
value) QueryBuilder table
queryBuilder = FilterByQueryBuilder :: forall (table :: Symbol).
QueryBuilder table
-> (ByteString, FilterOperator, Action) -> QueryBuilder table
FilterByQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: QueryBuilder table
queryBuilder, $sel:queryFilter:NewQueryBuilder :: (ByteString, FilterOperator, Action)
queryFilter = (ByteString
columnName, MatchSensitivity -> FilterOperator
LikeOp MatchSensitivity
CaseSensitive, value -> Action
forall a. ToField a => a -> Action
toField value
value) }
    where
        columnName :: ByteString
columnName = Text -> ByteString
Text.encodeUtf8 (Text -> Text
fieldNameToColumnName (KnownSymbol name => Text
forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name))
{-# INLINE filterWhereLike #-}

-- | Adds a @WHERE x ILIKE y@ condition to the query. Case-insensitive version of 'filterWhereLike'.
--
-- __Example:__ Find titles matching search term.
--
-- > articles <- query @Article
-- >     |> filterWhereILike (#title, "%" <> searchTerm <> "%")
-- >     |> fetch
-- > -- SELECT * FROM articles WHERE title ILIKE '%..%'
filterWhereILike :: forall name table model value. (KnownSymbol name, ToField value, HasField name model value, model ~ GetModelByTableName table) => (Proxy name, value) -> QueryBuilder table -> QueryBuilder table
filterWhereILike :: (Proxy name, value) -> QueryBuilder table -> QueryBuilder table
filterWhereILike (Proxy name
name, value
value) QueryBuilder table
queryBuilder = FilterByQueryBuilder :: forall (table :: Symbol).
QueryBuilder table
-> (ByteString, FilterOperator, Action) -> QueryBuilder table
FilterByQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: QueryBuilder table
queryBuilder, $sel:queryFilter:NewQueryBuilder :: (ByteString, FilterOperator, Action)
queryFilter = (ByteString
columnName, MatchSensitivity -> FilterOperator
LikeOp MatchSensitivity
CaseInsensitive, value -> Action
forall a. ToField a => a -> Action
toField value
value) }
    where
        columnName :: ByteString
columnName = Text -> ByteString
Text.encodeUtf8 (Text -> Text
fieldNameToColumnName (KnownSymbol name => Text
forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name))
{-# INLINE filterWhereILike #-}

-- | Adds a @WHERE x ~ y@ condition to the query.
--
-- __Example:__ Find names with titles in front.
--
-- > articles <- query @User
-- >     |> filterWhereMatches (#name, "^(M(rs|r|iss)|Dr|Sir). ")
-- >     |> fetch
-- > -- SELECT * FROM articles WHERE title ~ '^(M(rs|r|iss)|Dr|Sir). '
filterWhereMatches :: forall name table model value. (KnownSymbol name, ToField value, HasField name model value, model ~ GetModelByTableName table) => (Proxy name, value) -> QueryBuilder table -> QueryBuilder table
filterWhereMatches :: (Proxy name, value) -> QueryBuilder table -> QueryBuilder table
filterWhereMatches (Proxy name
name, value
value) QueryBuilder table
queryBuilder = FilterByQueryBuilder :: forall (table :: Symbol).
QueryBuilder table
-> (ByteString, FilterOperator, Action) -> QueryBuilder table
FilterByQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: QueryBuilder table
queryBuilder, $sel:queryFilter:NewQueryBuilder :: (ByteString, FilterOperator, Action)
queryFilter = (ByteString
columnName, MatchSensitivity -> FilterOperator
MatchesOp MatchSensitivity
CaseSensitive, value -> Action
forall a. ToField a => a -> Action
toField value
value) }
    where
        columnName :: ByteString
columnName = Text -> ByteString
Text.encodeUtf8 (Text -> Text
fieldNameToColumnName (KnownSymbol name => Text
forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name))
{-# INLINE filterWhereMatches #-}

-- | Adds a @WHERE x ~* y@ condition to the query. Case-insensitive version of 'filterWhereMatches'.
filterWhereIMatches :: forall name table model value. (KnownSymbol name, ToField value, HasField name model value, model ~ GetModelByTableName table) => (Proxy name, value) -> QueryBuilder table -> QueryBuilder table
filterWhereIMatches :: (Proxy name, value) -> QueryBuilder table -> QueryBuilder table
filterWhereIMatches (Proxy name
name, value
value) QueryBuilder table
queryBuilder = FilterByQueryBuilder :: forall (table :: Symbol).
QueryBuilder table
-> (ByteString, FilterOperator, Action) -> QueryBuilder table
FilterByQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: QueryBuilder table
queryBuilder, $sel:queryFilter:NewQueryBuilder :: (ByteString, FilterOperator, Action)
queryFilter = (ByteString
columnName, MatchSensitivity -> FilterOperator
MatchesOp MatchSensitivity
CaseInsensitive, value -> Action
forall a. ToField a => a -> Action
toField value
value) }
    where
        columnName :: ByteString
columnName = Text -> ByteString
Text.encodeUtf8 (Text -> Text
fieldNameToColumnName (KnownSymbol name => Text
forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name))
{-# INLINE filterWhereIMatches #-}

-- | Allows to add a custom raw sql where condition
--
-- If your query cannot be represented with 'filterWhereSql', take a look at 'IHP.ModelSupport.sqlQuery'.
--
-- __Example:__ Fetching all projects created in the last 24 hours.
--
-- > latestProjects <- query @Project
-- >     |> filterWhereSql (#startedAt, "< current_timestamp - interval '1 day'")
-- >     |> fetch
-- > -- SELECT * FROM projects WHERE started_at < current_timestamp - interval '1 day'
--
filterWhereSql :: forall name table model value. (KnownSymbol name, ToField value, HasField name model value, model ~ GetModelByTableName table) => (Proxy name, ByteString) -> QueryBuilder table -> QueryBuilder table
filterWhereSql :: (Proxy name, ByteString)
-> QueryBuilder table -> QueryBuilder table
filterWhereSql (Proxy name
name, ByteString
sqlCondition) QueryBuilder table
queryBuilder = FilterByQueryBuilder :: forall (table :: Symbol).
QueryBuilder table
-> (ByteString, FilterOperator, Action) -> QueryBuilder table
FilterByQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: QueryBuilder table
queryBuilder, $sel:queryFilter:NewQueryBuilder :: (ByteString, FilterOperator, Action)
queryFilter = (ByteString
columnName, FilterOperator
SqlOp, Builder -> Action
Plain (ByteString -> Builder
Builder.byteString ByteString
sqlCondition)) }
    where
        columnName :: ByteString
columnName = Text -> ByteString
Text.encodeUtf8 (Text -> Text
fieldNameToColumnName (KnownSymbol name => Text
forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name))
{-# INLINE filterWhereSql #-}

-- | Adds an @ORDER BY .. ASC@ to your query.
--
-- Use 'orderByDesc' for descending order.
--
-- __Example:__ Fetch the 10 oldest books.
--
-- > query @Book
-- >     |> orderBy #createdAt
-- >     |> limit 10
-- >     |> fetch
-- > -- SELECT * FROM books LIMIT 10 ORDER BY created_at ASC
orderByAsc :: forall name model table value. (KnownSymbol name, HasField name model value, model ~ GetModelByTableName table) => Proxy name -> QueryBuilder table -> QueryBuilder table
orderByAsc :: Proxy name -> QueryBuilder table -> QueryBuilder table
orderByAsc !Proxy name
name QueryBuilder table
queryBuilder = OrderByQueryBuilder :: forall (table :: Symbol).
QueryBuilder table -> OrderByClause -> QueryBuilder table
OrderByQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: QueryBuilder table
queryBuilder, $sel:queryOrderByClause:NewQueryBuilder :: OrderByClause
queryOrderByClause = OrderByClause :: ByteString -> OrderByDirection -> OrderByClause
OrderByClause { $sel:orderByColumn:OrderByClause :: ByteString
orderByColumn = ByteString
columnName, $sel:orderByDirection:OrderByClause :: OrderByDirection
orderByDirection = OrderByDirection
Asc } }
    where
        columnName :: ByteString
columnName = Text -> ByteString
Text.encodeUtf8 (Text -> Text
fieldNameToColumnName (KnownSymbol name => Text
forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name))
{-# INLINE orderByAsc #-}

-- | Adds an @ORDER BY .. DESC@ to your query.
--
-- Use 'orderBy' for ascending order.
--
-- __Example:__ Fetch the 10 newest projects (ordered by creation time).
--
-- > query @Project
-- >     |> orderBy #createdAt
-- >     |> limit 10
-- >     |> fetch
-- > -- SELECT * FROM projects LIMIT 10 ORDER BY created_at DESC
orderByDesc :: forall name model table value. (KnownSymbol name, HasField name model value, model ~ GetModelByTableName table) => Proxy name -> QueryBuilder table -> QueryBuilder table
orderByDesc :: Proxy name -> QueryBuilder table -> QueryBuilder table
orderByDesc !Proxy name
name QueryBuilder table
queryBuilder = OrderByQueryBuilder :: forall (table :: Symbol).
QueryBuilder table -> OrderByClause -> QueryBuilder table
OrderByQueryBuilder { QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: QueryBuilder table
queryBuilder, $sel:queryOrderByClause:NewQueryBuilder :: OrderByClause
queryOrderByClause = OrderByClause :: ByteString -> OrderByDirection -> OrderByClause
OrderByClause { $sel:orderByColumn:OrderByClause :: ByteString
orderByColumn = ByteString
columnName, $sel:orderByDirection:OrderByClause :: OrderByDirection
orderByDirection = OrderByDirection
Desc } }
    where
        columnName :: ByteString
columnName = Text -> ByteString
Text.encodeUtf8 (Text -> Text
fieldNameToColumnName (KnownSymbol name => Text
forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name))
{-# INLINE orderByDesc #-}

-- | Alias for 'orderByAsc'
orderBy :: (KnownSymbol name, HasField name model value, model ~ GetModelByTableName table) => Proxy name -> QueryBuilder table -> QueryBuilder table
orderBy :: Proxy name -> QueryBuilder table -> QueryBuilder table
orderBy !Proxy name
name = Proxy name -> QueryBuilder table -> QueryBuilder table
forall (name :: Symbol) model (table :: Symbol) value.
(KnownSymbol name, HasField name model value,
 model ~ GetModelByTableName table) =>
Proxy name -> QueryBuilder table -> QueryBuilder table
orderByAsc Proxy name
name
{-# INLINE orderBy #-}

-- | Adds an @LIMIT ..@ to your query.
--
--
-- __Example:__ Fetch 10 posts
--
-- > query @Post
-- >     |> limit 10
-- >     |> fetch
-- > -- SELECT * FROM posts LIMIT 10
limit :: Int -> QueryBuilder model -> QueryBuilder model
limit :: Int -> QueryBuilder model -> QueryBuilder model
limit !Int
queryLimit QueryBuilder model
queryBuilder = LimitQueryBuilder :: forall (table :: Symbol).
QueryBuilder table -> Int -> QueryBuilder table
LimitQueryBuilder { QueryBuilder model
queryBuilder :: QueryBuilder model
$sel:queryBuilder:NewQueryBuilder :: QueryBuilder model
queryBuilder, Int
queryLimit :: Int
$sel:queryLimit:NewQueryBuilder :: Int
queryLimit }
{-# INLINE limit #-}

-- | Adds an @OFFSET ..@ to your query. Most often used together with @LIMIT...@
--
--
-- __Example:__ Fetch posts 10-20
--
-- > query @Post
-- >     |> limit 10
-- >     |> offset 10
-- >     |> fetch
-- > -- SELECT * FROM posts LIMIT 10 OFFSET 10
offset :: Int -> QueryBuilder model -> QueryBuilder model
offset :: Int -> QueryBuilder model -> QueryBuilder model
offset !Int
queryOffset QueryBuilder model
queryBuilder = OffsetQueryBuilder :: forall (table :: Symbol).
QueryBuilder table -> Int -> QueryBuilder table
OffsetQueryBuilder { QueryBuilder model
queryBuilder :: QueryBuilder model
$sel:queryBuilder:NewQueryBuilder :: QueryBuilder model
queryBuilder, Int
queryOffset :: Int
$sel:queryOffset:NewQueryBuilder :: Int
queryOffset }
{-# INLINE offset #-}

-- | Merges the results of two query builders.
--
-- Take a look at ‘queryOr'  as well, as this might be a bit shorter.
--
-- __Example:__ Return all pages owned by the user or owned by the users team.
--
-- > let userPages = query @Page |> filterWhere (#ownerId, currentUserId)
-- > let teamPages = query @Page |> filterWhere (#teamId, currentTeamId)
-- > pages <- queryUnion userPages teamPages |> fetch
-- > -- (SELECT * FROM pages WHERE owner_id = '..') UNION (SELECT * FROM pages WHERE team_id = '..')
queryUnion :: QueryBuilder model -> QueryBuilder model -> QueryBuilder model
queryUnion :: QueryBuilder model -> QueryBuilder model -> QueryBuilder model
queryUnion QueryBuilder model
firstQueryBuilder QueryBuilder model
secondQueryBuilder = UnionQueryBuilder :: forall (table :: Symbol).
QueryBuilder table -> QueryBuilder table -> QueryBuilder table
UnionQueryBuilder { QueryBuilder model
firstQueryBuilder :: QueryBuilder model
$sel:firstQueryBuilder:NewQueryBuilder :: QueryBuilder model
firstQueryBuilder, QueryBuilder model
secondQueryBuilder :: QueryBuilder model
$sel:secondQueryBuilder:NewQueryBuilder :: QueryBuilder model
secondQueryBuilder }
{-# INLINE queryUnion #-}


-- | Adds an @a OR b@ condition
--
-- __Example:__ Return all pages owned by the user or public.
--
-- > query @Page
-- >     |> queryOr
-- >         (filterWhere (#createdBy, currentUserId))
-- >         (filterWhere (#public, True))
-- >     |> fetch
-- > -- SELECT * FROM pages WHERE created_by = '..' OR public = True
queryOr :: (qb ~ QueryBuilder model) => (qb -> qb) -> (qb -> qb) -> qb -> qb
queryOr :: (qb -> qb) -> (qb -> qb) -> qb -> qb
queryOr qb -> qb
firstQuery qb -> qb
secondQuery qb
queryBuilder = UnionQueryBuilder :: forall (table :: Symbol).
QueryBuilder table -> QueryBuilder table -> QueryBuilder table
UnionQueryBuilder { $sel:firstQueryBuilder:NewQueryBuilder :: QueryBuilder model
firstQueryBuilder = qb -> qb
firstQuery qb
queryBuilder, $sel:secondQueryBuilder:NewQueryBuilder :: QueryBuilder model
secondQueryBuilder = qb -> qb
secondQuery qb
queryBuilder }
{-# INLINE queryOr #-}

-- | Adds a @DISTINCT@ to your query.
--
-- Use 'distinct' to remove all duplicate rows from the result
--
-- __Example:__ Fetch distinct books
--
-- > query @Book
-- >     |> distinct
-- >     |> fetch
-- > -- SELECT DISTINCT * FROM books
distinct :: QueryBuilder table -> QueryBuilder table
distinct :: QueryBuilder table -> QueryBuilder table
distinct = QueryBuilder table -> QueryBuilder table
forall (table :: Symbol). QueryBuilder table -> QueryBuilder table
DistinctQueryBuilder
{-# INLINE distinct #-}

-- | Adds an @DISTINCT ON .. to your query.
--
-- Use 'distinctOn' to return a single row for each distinct value provided.
--
-- __Example:__ Fetch one book for each categoryId field
--
-- > query @Book
-- >     |> distinctOn #categoryId
-- >     |> fetch
-- > -- SELECT DISTINCT ON (category_id) * FROM books
distinctOn :: forall name model value table. (KnownSymbol name, HasField name model value, model ~ GetModelByTableName table) => Proxy name -> QueryBuilder table -> QueryBuilder table
distinctOn :: Proxy name -> QueryBuilder table -> QueryBuilder table
distinctOn !Proxy name
name QueryBuilder table
queryBuilder = DistinctOnQueryBuilder :: forall (table :: Symbol).
QueryBuilder table -> ByteString -> QueryBuilder table
DistinctOnQueryBuilder { $sel:distinctOnColumn:NewQueryBuilder :: ByteString
distinctOnColumn = ByteString
columnName, QueryBuilder table
queryBuilder :: QueryBuilder table
$sel:queryBuilder:NewQueryBuilder :: QueryBuilder table
queryBuilder }
    where
        columnName :: ByteString
columnName = Text -> ByteString
Text.encodeUtf8 (Text -> Text
fieldNameToColumnName (KnownSymbol name => Text
forall (symbol :: Symbol). KnownSymbol symbol => Text
symbolToText @name))
{-# INLINE distinctOn #-}



-- | Helper to deal with @some_field IS NULL@ and @some_field = 'some value'@
class EqOrIsOperator value where toEqOrIsOperator :: value -> FilterOperator
instance {-# OVERLAPS #-} EqOrIsOperator (Maybe something) where toEqOrIsOperator :: Maybe something -> FilterOperator
toEqOrIsOperator Maybe something
Nothing = FilterOperator
IsOp; toEqOrIsOperator (Just something
_) = FilterOperator
EqOp
instance {-# OVERLAPPABLE #-} EqOrIsOperator otherwise where toEqOrIsOperator :: otherwise -> FilterOperator
toEqOrIsOperator otherwise
_ = FilterOperator
EqOp